better-auth
Version:
The most comprehensive authentication framework for TypeScript.
213 lines (211 loc) • 8.19 kB
JavaScript
import { logger } from "@better-auth/core/env";
import { createAdapterFactory } from "@better-auth/core/db/adapter";
//#region src/adapters/memory-adapter/memory-adapter.ts
const memoryAdapter = (db, config) => {
let lazyOptions = null;
let adapterCreator = createAdapterFactory({
config: {
adapterId: "memory",
adapterName: "Memory Adapter",
usePlural: false,
debugLogs: config?.debugLogs || false,
customTransformInput(props) {
if ((props.options.advanced?.database?.useNumberId || props.options.advanced?.database?.generateId === "serial") && props.field === "id" && props.action === "create") return db[props.model].length + 1;
return props.data;
},
transaction: async (cb) => {
let clone = structuredClone(db);
try {
return await cb(adapterCreator(lazyOptions));
} catch (error) {
Object.keys(db).forEach((key) => {
db[key] = clone[key];
});
throw error;
}
}
},
adapter: ({ getFieldName, options, getModelName }) => {
const applySortToRecords = (records, sortBy, model) => {
if (!sortBy) return records;
return records.sort((a, b) => {
const field = getFieldName({
model,
field: sortBy.field
});
const aValue = a[field];
const bValue = b[field];
let comparison = 0;
if (aValue == null && bValue == null) comparison = 0;
else if (aValue == null) comparison = -1;
else if (bValue == null) comparison = 1;
else if (typeof aValue === "string" && typeof bValue === "string") comparison = aValue.localeCompare(bValue);
else if (aValue instanceof Date && bValue instanceof Date) comparison = aValue.getTime() - bValue.getTime();
else if (typeof aValue === "number" && typeof bValue === "number") comparison = aValue - bValue;
else if (typeof aValue === "boolean" && typeof bValue === "boolean") comparison = aValue === bValue ? 0 : aValue ? 1 : -1;
else comparison = String(aValue).localeCompare(String(bValue));
return sortBy.direction === "asc" ? comparison : -comparison;
});
};
function convertWhereClause(where, model, join) {
const execute = (where$1, model$1) => {
const table = db[model$1];
if (!table) {
logger.error(`[MemoryAdapter] Model ${model$1} not found in the DB`, Object.keys(db));
throw new Error(`Model ${model$1} not found`);
}
const evalClause = (record, clause) => {
const { field, value, operator } = clause;
switch (operator) {
case "in":
if (!Array.isArray(value)) throw new Error("Value must be an array");
return value.includes(record[field]);
case "not_in":
if (!Array.isArray(value)) throw new Error("Value must be an array");
return !value.includes(record[field]);
case "contains": return record[field].includes(value);
case "starts_with": return record[field].startsWith(value);
case "ends_with": return record[field].endsWith(value);
case "ne": return record[field] !== value;
case "gt": return value != null && Boolean(record[field] > value);
case "gte": return value != null && Boolean(record[field] >= value);
case "lt": return value != null && Boolean(record[field] < value);
case "lte": return value != null && Boolean(record[field] <= value);
default: return record[field] === value;
}
};
return table.filter((record) => {
if (!where$1.length || where$1.length === 0) return true;
let result = evalClause(record, where$1[0]);
for (const clause of where$1) {
const clauseResult = evalClause(record, clause);
if (clause.connector === "OR") result = result || clauseResult;
else result = result && clauseResult;
}
return result;
});
};
if (!join) return execute(where, model);
const baseRecords = execute(where, model);
const grouped = /* @__PURE__ */ new Map();
const seenIds = /* @__PURE__ */ new Map();
for (const baseRecord of baseRecords) {
const baseId = String(baseRecord.id);
if (!grouped.has(baseId)) {
const nested = { ...baseRecord };
for (const [joinModel, joinAttr] of Object.entries(join)) {
const joinModelName = getModelName(joinModel);
if (joinAttr.relation === "one-to-one") nested[joinModelName] = null;
else {
nested[joinModelName] = [];
seenIds.set(`${baseId}-${joinModel}`, /* @__PURE__ */ new Set());
}
}
grouped.set(baseId, nested);
}
const nestedEntry = grouped.get(baseId);
for (const [joinModel, joinAttr] of Object.entries(join)) {
const joinModelName = getModelName(joinModel);
const joinTable = db[joinModelName];
if (!joinTable) {
logger.error(`[MemoryAdapter] JoinOption model ${joinModelName} not found in the DB`, Object.keys(db));
throw new Error(`JoinOption model ${joinModelName} not found`);
}
const matchingRecords = joinTable.filter((joinRecord) => joinRecord[joinAttr.on.to] === baseRecord[joinAttr.on.from]);
if (joinAttr.relation === "one-to-one") nestedEntry[joinModelName] = matchingRecords[0] || null;
else {
const seenSet = seenIds.get(`${baseId}-${joinModel}`);
const limit = joinAttr.limit ?? 100;
let count = 0;
for (const matchingRecord of matchingRecords) {
if (count >= limit) break;
if (!seenSet.has(matchingRecord.id)) {
nestedEntry[joinModelName].push(matchingRecord);
seenSet.add(matchingRecord.id);
count++;
}
}
}
}
}
return Array.from(grouped.values());
}
return {
create: async ({ model, data }) => {
if (options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial") data.id = db[getModelName(model)].length + 1;
if (!db[model]) db[model] = [];
db[model].push(data);
return data;
},
findOne: async ({ model, where, join }) => {
const res = convertWhereClause(where, model, join);
if (join) {
const resArray = res;
if (!resArray.length) return null;
return resArray[0];
}
return res[0] || null;
},
findMany: async ({ model, where, sortBy, limit, offset, join }) => {
let res = convertWhereClause(where || [], model, join);
if (join) {
const resArray = res;
if (!resArray.length) return [];
applySortToRecords(resArray, sortBy, model);
let paginatedRecords = resArray;
if (offset !== void 0) paginatedRecords = paginatedRecords.slice(offset);
if (limit !== void 0) paginatedRecords = paginatedRecords.slice(0, limit);
return paginatedRecords;
}
let table = applySortToRecords(res, sortBy, model);
if (offset !== void 0) table = table.slice(offset);
if (limit !== void 0) table = table.slice(0, limit);
return table || [];
},
count: async ({ model, where }) => {
if (where) return convertWhereClause(where, model).length;
return db[model].length;
},
update: async ({ model, where, update }) => {
const res = convertWhereClause(where, model);
res.forEach((record) => {
Object.assign(record, update);
});
return res[0] || null;
},
delete: async ({ model, where }) => {
const table = db[model];
const res = convertWhereClause(where, model);
db[model] = table.filter((record) => !res.includes(record));
},
deleteMany: async ({ model, where }) => {
const table = db[model];
const res = convertWhereClause(where, model);
let count = 0;
db[model] = table.filter((record) => {
if (res.includes(record)) {
count++;
return false;
}
return !res.includes(record);
});
return count;
},
updateMany({ model, where, update }) {
const res = convertWhereClause(where, model);
res.forEach((record) => {
Object.assign(record, update);
});
return res[0] || null;
}
};
}
});
return (options) => {
lazyOptions = options;
return adapterCreator(options);
};
};
//#endregion
export { memoryAdapter };
//# sourceMappingURL=memory-adapter.mjs.map