better-auth-mikro-orm
Version:
Mikro ORM Adapter for Better Auth
360 lines (353 loc) • 12.3 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
mikroOrmAdapter: () => mikroOrmAdapter
});
module.exports = __toCommonJS(src_exports);
// src/adapter.ts
var import_adapters = require("better-auth/adapters");
var import_dset2 = require("dset");
// src/utils/adapterUtils.ts
var import_core = require("@mikro-orm/core");
var import_dset = require("dset");
// src/utils/createAdapterError.ts
var import_better_auth = require("better-auth");
function createAdapterError(message) {
throw new import_better_auth.BetterAuthError(`[Mikro ORM Adapter] ${message}`);
}
// src/utils/adapterUtils.ts
function checkForExhaustiveWhereOperator(op) {
throw new RangeError(
`[Better Auth MikroORM adapter error] Unhandled WHERE operator detected: ${op}`
);
}
var ownReferences = [
import_core.ReferenceKind.SCALAR,
import_core.ReferenceKind.ONE_TO_MANY,
import_core.ReferenceKind.EMBEDDED
];
function createAdapterUtils(orm, config) {
const naming = orm.config.getNamingStrategy();
const metadata = orm.getMetadata();
const normalizeEntityName = (name) => naming.getEntityName(naming.classToTableName(name));
const getEntityMetadata = (entityName) => {
const normalizedEntityName = normalizeEntityName(entityName);
if (!metadata.has(normalizedEntityName)) {
createAdapterError(
`Cannot find metadata for "${normalizedEntityName}" entity. Make sure it defined and listed in your Mikro ORM config.`
);
}
return metadata.get(normalizedEntityName);
};
function getPropertyMetadata(metadata2, fieldName) {
const prop = metadata2.props.find((prop2) => {
if (ownReferences.includes(prop2.kind) && prop2.name === fieldName) {
return true;
}
if (prop2.kind === import_core.ReferenceKind.MANY_TO_ONE && (prop2.name === fieldName || prop2.fieldNames.includes(naming.propertyToColumnName(fieldName)))) {
return true;
}
return false;
});
if (!prop) {
createAdapterError(
`Can't find property "${fieldName}" on entity "${metadata2.className}".`
);
}
return prop;
}
function getReferencedColumnName(entityName, prop) {
if (ownReferences.includes(prop.kind)) {
return prop.name;
}
if (prop.kind === import_core.ReferenceKind.MANY_TO_ONE) {
return naming.columnNameToProperty(naming.joinColumnName(prop.name));
}
createAdapterError(
`Reference kind ${prop.kind} is not supported. Defined in "${entityName}" entity for "${prop.name}" field.`
);
}
const getReferencedPropertyName = (metadata2, prop) => getReferencedColumnName(metadata2.className, prop);
const getFieldPath = (metadata2, fieldName, throwOnShadowProps = false) => {
const prop = getPropertyMetadata(metadata2, fieldName);
if (prop.persist === false && throwOnShadowProps) {
createAdapterError(
`Cannot serialize "${fieldName}" into path, because it cannot be persisted in "${metadata2.tableName}" table.`
);
}
if (prop.kind === import_core.ReferenceKind.SCALAR || prop.kind === import_core.ReferenceKind.EMBEDDED) {
return [prop.name];
}
if (prop.kind === import_core.ReferenceKind.MANY_TO_ONE) {
if (prop.referencedPKs.length > 1) {
createAdapterError(
`The "${fieldName}" field references to a table "${prop.name}" with complex primary key, which is not supported`
);
}
return [prop.name, naming.referenceColumnName()];
}
createAdapterError(
`Cannot normalize "${fieldName}" field name into path for "${metadata2.className}" entity.`
);
};
const normalizePropertyValue = (property, value) => {
if (!property.targetMeta || property.kind === import_core.ReferenceKind.SCALAR || property.kind === import_core.ReferenceKind.EMBEDDED) {
return value;
}
return orm.em.getReference(property.targetMeta.class, value);
};
const normalizeInput = (metadata2, input) => {
const fields = {};
Object.entries(input).forEach(([key, value]) => {
const property = getPropertyMetadata(metadata2, key);
const normalizedValue = normalizePropertyValue(property, value);
(0, import_dset.dset)(fields, [property.name], normalizedValue);
});
return fields;
};
const normalizeOutput = (metadata2, output, select) => {
let result = {};
const serializedOutput = (0, import_core.serialize)(output);
Object.entries(serializedOutput).map(([key, value]) => ({
path: getReferencedPropertyName(
metadata2,
getPropertyMetadata(metadata2, key)
),
value
})).forEach(({ path, value }) => {
(0, import_dset.dset)(result, path, value);
});
if (select) {
result = Object.fromEntries(
Object.entries(result).filter(([name]) => select.includes(name))
);
}
return result;
};
function createWhereClause(path, value, op, target = {}) {
(0, import_dset.dset)(target, op == null || op === "eq" ? path : path.concat(op), value);
return target;
}
function createWhereInClause(fieldName, path, value, operator, target) {
const normalizedOperator = `$${operator}`;
if (!Array.isArray(value)) {
createAdapterError(
`The value for the field "${fieldName}" must be an array when using the ${normalizedOperator} operator.`
);
}
return createWhereClause(path, value, normalizedOperator, target);
}
function normalizeWhereClause(path, input, target) {
switch (input.operator) {
case "in":
return createWhereInClause(input.field, path, input.value, "in", target);
case "not_in":
return createWhereInClause(
input.field,
path,
input.value,
"nin",
target
);
case "contains":
return createWhereClause(path, `%${input.value}%`, "$like", target);
case "starts_with":
return createWhereClause(path, `${input.value}%`, "$like", target);
case "ends_with":
return createWhereClause(path, `%${input.value}`, "$like", target);
// The next 5 case statemets are _expected_ to fall through so we can simplify and reuse the same logic for these operators
case "gt":
case "gte":
case "lt":
case "lte":
case "ne":
return createWhereClause(
path,
input.value,
`$${input.operator}`,
target
);
case "eq":
case void 0:
return createWhereClause(path, input.value, "eq", target);
default:
return checkForExhaustiveWhereOperator(input.operator);
}
}
const normalizeWhereClauses = (metadata2, where) => {
if (!where) {
return {};
}
if (where.length === 1) {
const [clause] = where;
if (!clause) {
return {};
}
const path = getFieldPath(metadata2, clause.field, true);
return normalizeWhereClause(path, clause);
}
const result = {};
where.filter(({ connector }) => !connector || connector === "AND").forEach((clause, index) => {
const path = ["$and", index].concat(
getFieldPath(metadata2, clause.field, true)
);
normalizeWhereClause(path, clause, result);
});
where.filter(({ connector }) => connector === "OR").forEach((clause, index) => {
const path = ["$or", index].concat(
getFieldPath(metadata2, clause.field, true)
);
normalizeWhereClause(path, clause, result);
});
return result;
};
const normalizeSelect = (model, select) => select?.map((field) => config.getFieldName({ model, field }));
return {
getEntityMetadata,
normalizeEntityName,
getFieldPath,
normalizeInput,
normalizeOutput,
normalizeWhereClauses,
normalizeSelect
};
}
// src/adapter.ts
var mikroOrmAdapter = (orm, { debugLogs, supportsJSON = true } = {}) => (0, import_adapters.createAdapterFactory)({
config: {
adapterId: "mikro-orm-adapter",
adapterName: "Mikro ORM Adapter",
debugLogs,
supportsJSON
},
adapter(config) {
const {
getEntityMetadata,
getFieldPath,
normalizeInput,
normalizeOutput,
normalizeWhereClauses,
normalizeSelect
} = createAdapterUtils(orm, config);
return {
async create({ model, data, select }) {
const metadata = getEntityMetadata(model);
const input = normalizeInput(metadata, data);
const entity = orm.em.create(metadata.class, input);
await orm.em.persistAndFlush(entity);
return normalizeOutput(
metadata,
entity,
normalizeSelect(model, select)
);
},
async count({ model, where }) {
const metadata = getEntityMetadata(model);
return orm.em.count(
metadata.class,
normalizeWhereClauses(metadata, where)
);
},
async findOne({ model, where, select }) {
const metadata = getEntityMetadata(model);
const entity = await orm.em.findOne(
metadata.class,
normalizeWhereClauses(metadata, where)
);
if (!entity) {
return null;
}
const result = normalizeOutput(
metadata,
entity,
normalizeSelect(model, select)
);
return result;
},
async findMany({ model, where, limit, offset, sortBy, select }) {
const metadata = getEntityMetadata(model);
const options = {
limit,
offset
};
if (sortBy) {
const path = getFieldPath(metadata, sortBy.field);
(0, import_dset2.dset)(options, ["orderBy", ...path], sortBy.direction);
}
const rows = await orm.em.find(
metadata.class,
normalizeWhereClauses(metadata, where),
options
);
const normalizedSelect = normalizeSelect(model, select);
const result = rows.map(
(row) => normalizeOutput(metadata, row, normalizedSelect)
);
return result;
},
async update({ model, where, update }) {
const metadata = getEntityMetadata(model);
const entity = await orm.em.findOne(
metadata.class,
normalizeWhereClauses(metadata, where)
);
if (!entity) {
return null;
}
orm.em.assign(entity, normalizeInput(metadata, update));
await orm.em.flush();
return normalizeOutput(metadata, entity);
},
async updateMany({ model, where, update }) {
const metadata = getEntityMetadata(model);
return orm.em.nativeUpdate(
metadata.class,
normalizeWhereClauses(metadata, where),
normalizeInput(metadata, update)
);
},
async delete({ model, where }) {
const metadata = getEntityMetadata(model);
const entity = await orm.em.findOne(
metadata.class,
normalizeWhereClauses(metadata, where),
{
fields: ["id"]
}
);
if (entity) {
await orm.em.removeAndFlush(entity);
}
},
async deleteMany({ model, where }) {
const metadata = getEntityMetadata(model);
return orm.em.nativeDelete(
metadata.class,
normalizeWhereClauses(metadata, where)
);
}
};
}
});
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
mikroOrmAdapter
});