@payload-auth/better-auth-db-adapter
Version:
Payload CMS database adapter for BetterAuth
252 lines (251 loc) • 9.8 kB
JavaScript
import { BetterAuthError } from "better-auth";
import { getAuthTables } from "better-auth/db";
export const createTransform = (options, enableDebugLogs)=>{
const schema = getAuthTables(options);
function debugLog(message) {
if (enableDebugLogs) {
console.log(`[payload-db-adapter]`, ...message);
}
}
function getField(model, field) {
if (field === "id") {
return field;
}
const f = schema[model]?.fields[field];
const fieldName = f?.fieldName || field;
debugLog([
"getField: ",
model,
fieldName
]);
return fieldName;
}
function getModelName(model) {
const collection = schema[model]?.modelName || model;
if (!collection) {
throw new BetterAuthError(`Model ${model} does not exist in the database.`);
}
return collection;
}
function singleIdQuery(where) {
if (!where || "and" in where || "or" in where) return null;
// For a single id query like { id: { equals: 15 } }
// First, check if there's an id field in the where clause
if ("id" in where || "_id" in where) {
const idField = "id" in where ? "id" : "_id";
const condition = where[idField];
// Check if condition is an object with equals operator
if (condition && typeof condition === "object" && !Array.isArray(condition) && "equals" in condition) {
const value = condition.equals;
if (typeof value === "string" || typeof value === "number") {
return value;
}
}
// Check for contains operator with single value
if (condition && typeof condition === "object" && !Array.isArray(condition) && "contains" in condition && Array.isArray(condition.contains) && condition.contains.length === 1) {
const value = condition.contains[0];
if (typeof value === "string" || typeof value === "number") {
return value;
}
}
}
return null;
}
function multipleIdsQuery(where) {
if (!where || "and" in where || "or" in where) return null;
if ("id" in where || "_id" in where) {
const idField = "id" in where ? "id" : "_id";
const condition = where[idField];
// Check if this is an 'in' operator with id field and array of values
if (condition && typeof condition === "object" && !Array.isArray(condition) && "in" in condition && Array.isArray(condition.in) && condition.in.length > 1 && condition.in.every((id)=>typeof id === "string" || typeof id === "number")) {
return condition.in;
}
// Also check for contains operator with array of IDs
if (condition && typeof condition === "object" && !Array.isArray(condition) && "contains" in condition && Array.isArray(condition.contains) && condition.contains.length > 1 && condition.contains.every((id)=>typeof id === "string" || typeof id === "number")) {
return condition.contains;
}
}
return null;
}
function transformInput(data, model, action) {
const transformedData = {};
const schemaFields = schema[model].fields;
for(const dataField in data){
if (data[dataField] === undefined && action === "update") {
continue;
}
const updatedFieldName = schemaFields[dataField]?.fieldName;
if (updatedFieldName) {
if (schemaFields[dataField].type === "string" && typeof data[dataField] === "number" && updatedFieldName.endsWith("Id")) {
debugLog([
"Incoming data is typeof number but stored as typeof string",
dataField,
data[dataField].toString()
]);
transformedData[updatedFieldName] = data[dataField].toString();
} else {
transformedData[updatedFieldName] = data[dataField];
}
} else {
transformedData[dataField] = data[dataField];
}
}
return transformedData;
}
function transformOutput(doc) {
if (!doc || typeof doc !== "object") return doc;
const result = {
...doc
};
// Scan for relationship fields that contain objects with IDs
Object.entries(doc).forEach(([key, value])=>{
// If the value is an object with an id property, it's likely a relationship
if (value && typeof value === "object" && "id" in value) {
// Create a new field with Id suffix containing just the ID
const newKey = `${key}Id`;
result[newKey] = value.id;
//also remove the object and just keep the id
result[key] = value.id;
// Keep the original value as well for backward compatibility
} else if (Array.isArray(value)) {
// Handle arrays of relationships
if (value.length > 0 && typeof value[0] === "object" && "id" in value[0]) {
const newKey = `${key}Ids`;
result[newKey] = value.map((item)=>item.id);
//also remove the object and just keep the id
result[key] = value.map((item)=>item.id);
}
}
});
// Scan for date fields and convert them to Date objects
Object.entries(result).forEach(([key, value])=>{
// Check if the field is a date string (ISO format)
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(value)) {
result[key] = new Date(value);
} else if (// Also check for date-like field names
(key.endsWith("At") || key.endsWith("Date") || key === "date") && typeof value === "string" && !isNaN(Date.parse(value))) {
result[key] = new Date(value);
}
});
return result;
}
function operatorToPayload(operator, value) {
switch(operator){
case "eq":
return {
equals: value
};
case "ne":
return {
not_equals: value
};
case "gt":
return {
greater_than: value
};
case "gte":
return {
greater_than_equal: value
};
case "lt":
return {
less_than: value
};
case "lte":
return {
less_than_equal: value
};
case "contains":
return {
contains: value
};
case "in":
return {
in: value
};
case "starts_with":
return {
like: `${value}%`
};
case "ends_with":
return {
like: `%${value}`
};
default:
return {
equals: value
};
}
}
function convertWhereValue(value, field) {
if (field === "id" || field === "_id") {
if (typeof value === "object") {
return value.id;
}
return value;
}
return value;
}
function convertWhereClause(model, where) {
if (!where) return {};
if (where.length === 1) {
const w = where[0];
if (!w) {
return {};
}
const field = getField(model, w.field);
const value = convertWhereValue(w.value, field);
const res = {
[field]: operatorToPayload(w.operator ?? "", value)
};
return res;
}
const and = where.filter((w)=>w.connector === "AND" || !w.connector);
const or = where.filter((w)=>w.connector === "OR");
const andClause = and.map((w)=>{
const field = getField(model, w.field);
const value = convertWhereValue(w.value, field);
return {
[field]: operatorToPayload(w.operator ?? "", value)
};
});
const orClause = or.map((w)=>{
const field = getField(model, w.field);
const value = convertWhereValue(w.value, field);
return {
[field]: operatorToPayload(w.operator ?? "", value)
};
});
return {
...andClause.length ? {
AND: andClause
} : {},
...orClause.length ? {
OR: orClause
} : {}
};
}
function convertSelect(model, select) {
if (!select || select.length === 0) return undefined;
return select.reduce((acc, field)=>({
...acc,
[getField(model, field)]: true
}), {});
}
function convertSort(model, sortBy) {
if (!sortBy) return undefined;
return `${sortBy.direction === "desc" ? "-" : ""}${getField(model, sortBy.field)}`;
}
return {
getField,
getModelName,
singleIdQuery,
multipleIdsQuery,
transformInput,
transformOutput,
convertWhereClause,
convertSelect,
convertSort
};
};
//# sourceMappingURL=index.js.map