better-auth
Version:
The most comprehensive authentication framework for TypeScript.
318 lines (316 loc) • 14.9 kB
JavaScript
import { logger } from "@better-auth/core/env";
import { BetterAuthError } from "@better-auth/core/error";
import { createAdapterFactory } from "@better-auth/core/db/adapter";
import { and, asc, count, desc, eq, gt, gte, inArray, like, lt, lte, ne, notInArray, or, sql } from "drizzle-orm";
//#region src/adapters/drizzle-adapter/drizzle-adapter.ts
const drizzleAdapter = (db, config) => {
let lazyOptions = null;
const createCustomAdapter = (db$1) => ({ getFieldName, getDefaultFieldName, options }) => {
function getSchema(model) {
const schema = config.schema || db$1._.fullSchema;
if (!schema) throw new BetterAuthError("Drizzle adapter failed to initialize. Schema not found. Please provide a schema object in the adapter options object.");
const schemaModel = schema[model];
if (!schemaModel) throw new BetterAuthError(`[# Drizzle Adapter]: The model "${model}" was not found in the schema object. Please pass the schema directly to the adapter options.`);
return schemaModel;
}
const withReturning = async (model, builder, data, where) => {
if (config.provider !== "mysql") return (await builder.returning())[0];
await builder.execute();
const schemaModel = getSchema(model);
const builderVal = builder.config?.values;
if (where?.length) {
const clause = convertWhereClause(where.map((w) => {
if (data[w.field] !== void 0) return {
...w,
value: data[w.field]
};
return w;
}), model);
return (await db$1.select().from(schemaModel).where(...clause))[0];
} else if (builderVal && builderVal[0]?.id?.value) {
let tId = builderVal[0]?.id?.value;
if (!tId) tId = (await db$1.select({ id: sql`LAST_INSERT_ID()` }).from(schemaModel).orderBy(desc(schemaModel.id)).limit(1))[0].id;
return (await db$1.select().from(schemaModel).where(eq(schemaModel.id, tId)).limit(1).execute())[0];
} else if (data.id) return (await db$1.select().from(schemaModel).where(eq(schemaModel.id, data.id)).limit(1).execute())[0];
else {
if (!("id" in schemaModel)) throw new BetterAuthError(`The model "${model}" does not have an "id" field. Please use the "id" field as your primary key.`);
return (await db$1.select().from(schemaModel).orderBy(desc(schemaModel.id)).limit(1).execute())[0];
}
};
function convertWhereClause(where, model) {
const schemaModel = getSchema(model);
if (!where) return [];
if (where.length === 1) {
const w = where[0];
if (!w) return [];
const field = getFieldName({
model,
field: w.field
});
if (!schemaModel[field]) throw new BetterAuthError(`The field "${w.field}" does not exist in the schema for the model "${model}". Please update your schema.`);
if (w.operator === "in") {
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "in" operator.`);
return [inArray(schemaModel[field], w.value)];
}
if (w.operator === "not_in") {
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "not_in" operator.`);
return [notInArray(schemaModel[field], w.value)];
}
if (w.operator === "contains") return [like(schemaModel[field], `%${w.value}%`)];
if (w.operator === "starts_with") return [like(schemaModel[field], `${w.value}%`)];
if (w.operator === "ends_with") return [like(schemaModel[field], `%${w.value}`)];
if (w.operator === "lt") return [lt(schemaModel[field], w.value)];
if (w.operator === "lte") return [lte(schemaModel[field], w.value)];
if (w.operator === "ne") return [ne(schemaModel[field], w.value)];
if (w.operator === "gt") return [gt(schemaModel[field], w.value)];
if (w.operator === "gte") return [gte(schemaModel[field], w.value)];
return [eq(schemaModel[field], w.value)];
}
const andGroup = where.filter((w) => w.connector === "AND" || !w.connector);
const orGroup = where.filter((w) => w.connector === "OR");
const andClause = and(...andGroup.map((w) => {
const field = getFieldName({
model,
field: w.field
});
if (w.operator === "in") {
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "in" operator.`);
return inArray(schemaModel[field], w.value);
}
if (w.operator === "not_in") {
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "not_in" operator.`);
return notInArray(schemaModel[field], w.value);
}
if (w.operator === "contains") return like(schemaModel[field], `%${w.value}%`);
if (w.operator === "starts_with") return like(schemaModel[field], `${w.value}%`);
if (w.operator === "ends_with") return like(schemaModel[field], `%${w.value}`);
if (w.operator === "lt") return lt(schemaModel[field], w.value);
if (w.operator === "lte") return lte(schemaModel[field], w.value);
if (w.operator === "gt") return gt(schemaModel[field], w.value);
if (w.operator === "gte") return gte(schemaModel[field], w.value);
if (w.operator === "ne") return ne(schemaModel[field], w.value);
return eq(schemaModel[field], w.value);
}));
const orClause = or(...orGroup.map((w) => {
const field = getFieldName({
model,
field: w.field
});
if (w.operator === "in") {
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "in" operator.`);
return inArray(schemaModel[field], w.value);
}
if (w.operator === "not_in") {
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "not_in" operator.`);
return notInArray(schemaModel[field], w.value);
}
if (w.operator === "contains") return like(schemaModel[field], `%${w.value}%`);
if (w.operator === "starts_with") return like(schemaModel[field], `${w.value}%`);
if (w.operator === "ends_with") return like(schemaModel[field], `%${w.value}`);
if (w.operator === "lt") return lt(schemaModel[field], w.value);
if (w.operator === "lte") return lte(schemaModel[field], w.value);
if (w.operator === "gt") return gt(schemaModel[field], w.value);
if (w.operator === "gte") return gte(schemaModel[field], w.value);
if (w.operator === "ne") return ne(schemaModel[field], w.value);
return eq(schemaModel[field], w.value);
}));
const clause = [];
if (andGroup.length) clause.push(andClause);
if (orGroup.length) clause.push(orClause);
return clause;
}
function checkMissingFields(schema, model, values) {
if (!schema) throw new BetterAuthError("Drizzle adapter failed to initialize. Drizzle Schema not found. Please provide a schema object in the adapter options object.");
for (const key in values) if (!schema[key]) throw new BetterAuthError(`The field "${key}" does not exist in the "${model}" Drizzle schema. Please update your drizzle schema or re-generate using "npx @better-auth/cli@latest generate".`);
}
return {
async create({ model, data: values }) {
const schemaModel = getSchema(model);
checkMissingFields(schemaModel, model, values);
return await withReturning(model, db$1.insert(schemaModel).values(values), values);
},
async findOne({ model, where, select, join }) {
const schemaModel = getSchema(model);
const clause = convertWhereClause(where, model);
if (options.experimental?.joins) if (!db$1.query || !db$1.query[model]) {
logger.error(`[# Drizzle Adapter]: The model "${model}" was not found in the query object. Please update your Drizzle schema to include relations or re-generate using "npx @better-auth/cli@latest generate".`);
logger.info("Falling back to regular query");
} else {
let includes;
const pluralJoinResults = [];
if (join) {
includes = {};
const joinEntries = Object.entries(join);
for (const [model$1, joinAttr] of joinEntries) {
const limit = joinAttr.limit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
const isUnique = joinAttr.relation === "one-to-one";
const pluralSuffix = isUnique || config.usePlural ? "" : "s";
includes[`${model$1}${pluralSuffix}`] = isUnique ? true : { limit };
if (!isUnique) pluralJoinResults.push(`${model$1}${pluralSuffix}`);
}
}
const res$1 = await db$1.query[model].findFirst({
where: clause[0],
columns: select?.length && select.length > 0 ? select.reduce((acc, field) => {
acc[getFieldName({
model,
field
})] = true;
return acc;
}, {}) : void 0,
with: includes
});
if (res$1) for (const pluralJoinResult of pluralJoinResults) {
const singularKey = !config.usePlural ? pluralJoinResult.slice(0, -1) : pluralJoinResult;
res$1[singularKey] = res$1[pluralJoinResult];
if (pluralJoinResult !== singularKey) delete res$1[pluralJoinResult];
}
return res$1;
}
const res = await db$1.select(select?.length && select.length > 0 ? select.reduce((acc, field) => {
const fieldName = getFieldName({
model,
field
});
return {
...acc,
[fieldName]: schemaModel[fieldName]
};
}, {}) : void 0).from(schemaModel).where(...clause);
if (!res.length) return null;
return res[0];
},
async findMany({ model, where, sortBy, limit, select, offset, join }) {
const schemaModel = getSchema(model);
const clause = where ? convertWhereClause(where, model) : [];
const sortFn = sortBy?.direction === "desc" ? desc : asc;
if (options.experimental?.joins) if (!db$1.query[model]) {
logger.error(`[# Drizzle Adapter]: The model "${model}" was not found in the query object. Please update your Drizzle schema to include relations or re-generate using "npx @better-auth/cli@latest generate".`);
logger.info("Falling back to regular query");
} else {
let includes;
const pluralJoinResults = [];
if (join) {
includes = {};
const joinEntries = Object.entries(join);
for (const [model$1, joinAttr] of joinEntries) {
const isUnique = joinAttr.relation === "one-to-one";
const limit$1 = joinAttr.limit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
const pluralSuffix = isUnique || config.usePlural ? "" : "s";
includes[`${model$1}${pluralSuffix}`] = isUnique ? true : { limit: limit$1 };
if (!isUnique) pluralJoinResults.push(`${model$1}${pluralSuffix}`);
}
}
let orderBy = void 0;
if (sortBy?.field) orderBy = [sortFn(schemaModel[getFieldName({
model,
field: sortBy?.field
})])];
const res = await db$1.query[model].findMany({
where: clause[0],
with: includes,
columns: select?.length && select.length > 0 ? select.reduce((acc, field) => {
acc[getFieldName({
model,
field
})] = true;
return acc;
}, {}) : void 0,
limit: limit ?? 100,
offset: offset ?? 0,
orderBy
});
if (res) for (const item of res) for (const pluralJoinResult of pluralJoinResults) {
const singularKey = !config.usePlural ? pluralJoinResult.slice(0, -1) : pluralJoinResult;
if (singularKey === pluralJoinResult) continue;
item[singularKey] = item[pluralJoinResult];
delete item[pluralJoinResult];
}
return res;
}
let builder = db$1.select(select?.length && select.length > 0 ? select.reduce((acc, field) => {
const fieldName = getFieldName({
model,
field
});
return {
...acc,
[fieldName]: schemaModel[fieldName]
};
}, {}) : void 0).from(schemaModel);
const effectiveLimit = limit;
const effectiveOffset = offset;
if (typeof effectiveLimit !== "undefined") builder = builder.limit(effectiveLimit);
if (typeof effectiveOffset !== "undefined") builder = builder.offset(effectiveOffset);
if (sortBy?.field) builder = builder.orderBy(sortFn(schemaModel[getFieldName({
model,
field: sortBy?.field
})]));
return await builder.where(...clause);
},
async count({ model, where }) {
const schemaModel = getSchema(model);
const clause = where ? convertWhereClause(where, model) : [];
return (await db$1.select({ count: count() }).from(schemaModel).where(...clause))[0].count;
},
async update({ model, where, update: values }) {
const schemaModel = getSchema(model);
const clause = convertWhereClause(where, model);
return await withReturning(model, db$1.update(schemaModel).set(values).where(...clause), values, where);
},
async updateMany({ model, where, update: values }) {
const schemaModel = getSchema(model);
const clause = convertWhereClause(where, model);
return await db$1.update(schemaModel).set(values).where(...clause);
},
async delete({ model, where }) {
const schemaModel = getSchema(model);
const clause = convertWhereClause(where, model);
return await db$1.delete(schemaModel).where(...clause);
},
async deleteMany({ model, where }) {
const schemaModel = getSchema(model);
const clause = convertWhereClause(where, model);
const res = await db$1.delete(schemaModel).where(...clause);
let count$1 = 0;
if (res && "rowCount" in res) count$1 = res.rowCount;
else if (Array.isArray(res)) count$1 = res.length;
else if (res && ("affectedRows" in res || "rowsAffected" in res || "changes" in res)) count$1 = res.affectedRows ?? res.rowsAffected ?? res.changes;
if (typeof count$1 !== "number") logger.error("[Drizzle Adapter] The result of the deleteMany operation is not a number. This is likely a bug in the adapter. Please report this issue to the Better Auth team.", {
res,
model,
where
});
return count$1;
},
options: config
};
};
let adapterOptions = null;
adapterOptions = {
config: {
adapterId: "drizzle",
adapterName: "Drizzle Adapter",
usePlural: config.usePlural ?? false,
debugLogs: config.debugLogs ?? false,
supportsUUIDs: config.provider === "pg" ? true : false,
supportsJSON: config.provider === "pg" ? true : false,
supportsArrays: config.provider === "pg" ? true : false,
transaction: config.transaction ?? false ? (cb) => db.transaction((tx) => {
return cb(createAdapterFactory({
config: adapterOptions.config,
adapter: createCustomAdapter(tx)
})(lazyOptions));
}) : false
},
adapter: createCustomAdapter(db)
};
const adapter = createAdapterFactory(adapterOptions);
return (options) => {
lazyOptions = options;
return adapter(options);
};
};
//#endregion
export { drizzleAdapter };
//# sourceMappingURL=drizzle-adapter.mjs.map