Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@better-auth/memory-adapter

Package Overview
Dependencies
Maintainers
2
Versions
49
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@better-auth/memory-adapter - npm Package Compare versions

Comparing version
1.6.16
to
1.6.17
+109
-27
dist/index.mjs

@@ -33,5 +33,81 @@ import { createAdapterFactory } from "@better-auth/core/db/adapter";

//#region src/memory-adapter.ts
/**
* Index a table's rows by their `id` for row-level reconciliation. Every
* better-auth row carries an `id` (the adapter's join logic already keys on
* `record.id`), so the id is a stable identity for the three-way merge.
*/
function indexById(rows) {
const byId = /* @__PURE__ */ new Map();
for (const row of rows) byId.set(row.id, row);
return byId;
}
/**
* Commit a transaction onto the live database with a three-way merge so a
* concurrent write that interleaved at an `await` point survives.
*
* `base` is the snapshot taken when the transaction started; `clone` is that
* snapshot after the transaction mutated it; `target` is the live database as
* it stands now (possibly carrying concurrent writes). Replaying only the
* `base -> clone` delta onto `target` applies the transaction's own creates,
* updates, and deletes without disturbing rows or tables the transaction never
* touched. A row the transaction did not change keeps the live version, so a
* concurrent edit to a different row is preserved. A row the transaction did
* change wins last-writer-wins over a concurrent edit to the same row, which is
* acceptable for an in-memory development adapter; isolation is guaranteed only
* at row/table granularity.
*
* `target` is mutated in place so any reference held elsewhere (for example the
* `db` object the caller passed to `memoryAdapter`) stays valid.
*/
function mergeTransactionInto(target, base, clone) {
const models = new Set([...Object.keys(base), ...Object.keys(clone)]);
for (const model of models) {
if (!(model in clone)) {
delete target[model];
continue;
}
const baseById = indexById(base[model] ?? []);
const cloneRows = clone[model] ?? [];
const cloneById = indexById(cloneRows);
const liveRows = target[model] ?? [];
const merged = [];
const placed = /* @__PURE__ */ new Set();
for (const liveRow of liveRows) {
const id = liveRow.id;
const baseRow = baseById.get(id);
const cloneRow = cloneById.get(id);
if (baseRow !== void 0 && cloneRow === void 0) continue;
if (cloneRow !== void 0 && rowChanged(baseRow, cloneRow)) merged.push(cloneRow);
else merged.push(liveRow);
placed.add(id);
}
for (const cloneRow of cloneRows) if (!baseById.has(cloneRow.id) && !placed.has(cloneRow.id)) merged.push(cloneRow);
target[model] = merged;
}
}
/**
* Whether the transaction mutated a row, comparing its pre-transaction
* snapshot to its post-transaction state. Rows hold scalar columns and
* adapter-serializable values, so JSON equality reliably tells a
* transaction-made edit apart from an untouched row.
*/
function rowChanged(baseRow, cloneRow) {
if (baseRow === void 0) return true;
return JSON.stringify(baseRow) !== JSON.stringify(cloneRow);
}
const memoryAdapter = (db, config) => {
let lazyOptions = null;
const adapterCreator = createAdapterFactory({
/**
* Build an adapter factory whose operations read and write `activeDb`.
* The non-transactional adapter targets the live `db`. A transaction
* targets an isolated clone so its uncommitted writes are invisible to
* concurrent operations against the live `db`. A failed transaction leaves
* the live `db` untouched, and a committed one replays only its own
* row/table changes, so a concurrent write that interleaved at an `await`
* point survives either outcome. Isolation is at row/table granularity:
* the in-memory adapter does not serialize writes, so two operations that
* edit the same row resolve last-writer-wins. It is built for development
* and tests, not production concurrency control.
*/
const buildAdapterFactory = (activeDb) => createAdapterFactory({
config: {

@@ -44,15 +120,11 @@ adapterId: "memory",

customTransformInput(props) {
if (props.options.advanced?.database?.generateId === "serial" && props.field === "id" && props.action === "create") return db[props.model].length + 1;
if (props.options.advanced?.database?.generateId === "serial" && props.field === "id" && props.action === "create") return activeDb[props.model].length + 1;
return props.data;
},
transaction: async (cb) => {
const clone = structuredClone(db);
try {
return await cb(adapterCreator(lazyOptions));
} catch (error) {
Object.keys(db).forEach((key) => {
db[key] = clone[key];
});
throw error;
}
const base = structuredClone(activeDb);
const clone = structuredClone(activeDb);
const result = await cb(buildAdapterFactory(clone)(lazyOptions));
mergeTransactionInto(activeDb, base, clone);
return result;
}

@@ -84,5 +156,5 @@ },

const baseRecords = (() => {
const table = db[model];
const table = activeDb[model];
if (!table) {
logger.error(`[MemoryAdapter] Model ${model} not found in the DB`, Object.keys(db));
logger.error(`[MemoryAdapter] Model ${model} not found in the DB`, Object.keys(activeDb));
throw new Error(`Model ${model} not found`);

@@ -158,5 +230,5 @@ }

const joinModelName = getModelName(joinModel);
const joinTable = db[joinModelName];
const joinTable = activeDb[joinModelName];
if (!joinTable) {
logger.error(`[MemoryAdapter] JoinOption model ${joinModelName} not found in the DB`, Object.keys(db));
logger.error(`[MemoryAdapter] JoinOption model ${joinModelName} not found in the DB`, Object.keys(activeDb));
throw new Error(`JoinOption model ${joinModelName} not found`);

@@ -185,5 +257,5 @@ }

create: async ({ model, data }) => {
if (options.advanced?.database?.generateId === "serial") data.id = db[getModelName(model)].length + 1;
if (!db[model]) db[model] = [];
db[model].push(data);
if (options.advanced?.database?.generateId === "serial") data.id = activeDb[getModelName(model)].length + 1;
if (!activeDb[model]) activeDb[model] = [];
activeDb[model].push(data);
return data;

@@ -218,5 +290,6 @@ },

if (where) return convertWhereClause(where, model).length;
return db[model].length;
return activeDb[model].length;
},
update: async ({ model, where, update }) => {
if (where.length === 0) return null;
const res = convertWhereClause(where, model);

@@ -229,11 +302,12 @@ res.forEach((record) => {

delete: async ({ model, where }) => {
const table = db[model];
if (where.length === 0) return;
const table = activeDb[model];
const res = convertWhereClause(where, model);
db[model] = table.filter((record) => !res.includes(record));
activeDb[model] = table.filter((record) => !res.includes(record));
},
deleteMany: async ({ model, where }) => {
const table = db[model];
const table = activeDb[model];
const res = convertWhereClause(where, model);
let count = 0;
db[model] = table.filter((record) => {
activeDb[model] = table.filter((record) => {
if (res.includes(record)) {

@@ -248,9 +322,16 @@ count++;

consumeOne: async ({ model, where }) => {
const table = db[model];
const table = activeDb[model];
const target = convertWhereClause(where, model)[0];
if (!target) return null;
db[model] = table.filter((record) => record !== target);
activeDb[model] = table.filter((record) => record !== target);
return target;
},
updateMany({ model, where, update }) {
incrementOne: async ({ model, where, increment, set }) => {
const target = convertWhereClause(where, model)[0];
if (!target) return null;
for (const [field, delta] of Object.entries(increment)) target[field] = (typeof target[field] === "number" ? target[field] : 0) + delta;
if (set) Object.assign(target, set);
return target;
},
updateMany: async ({ model, where, update }) => {
const res = convertWhereClause(where, model);

@@ -260,3 +341,3 @@ res.forEach((record) => {

});
return res[0] || null;
return res.length;
}

@@ -266,2 +347,3 @@ };

});
const adapterCreator = buildAdapterFactory(db);
return (options) => {

@@ -268,0 +350,0 @@ lazyOptions = options;

+3
-3
{
"name": "@better-auth/memory-adapter",
"version": "1.6.16",
"version": "1.6.17",
"description": "Memory adapter for Better Auth",

@@ -39,3 +39,3 @@ "type": "module",

"@better-auth/utils": "0.4.1",
"@better-auth/core": "^1.6.16"
"@better-auth/core": "^1.6.17"
},

@@ -46,3 +46,3 @@ "devDependencies": {

"typescript": "^5.9.3",
"@better-auth/core": "1.6.16"
"@better-auth/core": "1.6.17"
},

@@ -49,0 +49,0 @@ "scripts": {