@better-auth/memory-adapter 1.5.0-beta.17 → 1.5.0-beta.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # Better Auth Memory Adapter
2
+
3
+ In-memory adapter for [Better Auth](https://www.better-auth.com) — useful for development and testing.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @better-auth/memory-adapter
9
+ ```
10
+
11
+ ## Documentation
12
+
13
+ For full documentation, visit [better-auth.com](https://www.better-auth.com).
14
+
15
+ ## License
16
+
17
+ MIT
package/package.json CHANGED
@@ -1,13 +1,28 @@
1
1
  {
2
2
  "name": "@better-auth/memory-adapter",
3
- "version": "1.5.0-beta.17",
3
+ "version": "1.5.0-beta.19",
4
4
  "description": "Memory adapter for Better Auth",
5
5
  "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://www.better-auth.com",
6
8
  "repository": {
7
9
  "type": "git",
8
10
  "url": "git+https://github.com/better-auth/better-auth.git",
9
11
  "directory": "packages/memory-adapter"
10
12
  },
13
+ "keywords": [
14
+ "auth",
15
+ "memory",
16
+ "adapter",
17
+ "typescript",
18
+ "better-auth"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
11
26
  "main": "./dist/index.mjs",
12
27
  "module": "./dist/index.mjs",
13
28
  "types": "./dist/index.d.mts",
@@ -20,18 +35,20 @@
20
35
  },
21
36
  "peerDependencies": {
22
37
  "@better-auth/utils": "^0.3.0",
23
- "@better-auth/core": "1.5.0-beta.17"
38
+ "@better-auth/core": "1.5.0-beta.19"
24
39
  },
25
40
  "devDependencies": {
26
41
  "@better-auth/utils": "^0.3.1",
27
42
  "tsdown": "^0.20.3",
28
43
  "typescript": "^5.9.3",
29
- "@better-auth/core": "1.5.0-beta.17"
44
+ "@better-auth/core": "1.5.0-beta.19"
30
45
  },
31
46
  "scripts": {
32
47
  "build": "tsdown",
33
48
  "dev": "tsdown --watch",
34
- "test": "vitest",
35
- "typecheck": "tsc --noEmit"
49
+ "lint:package": "publint run --strict",
50
+ "lint:types": "attw --profile esm-only --pack .",
51
+ "typecheck": "tsc --noEmit",
52
+ "test": "vitest"
36
53
  }
37
54
  }
@@ -1,16 +0,0 @@
1
-
2
- > @better-auth/memory-adapter@1.5.0-beta.17 build /home/runner/work/better-auth/better-auth/packages/memory-adapter
3
- > tsdown
4
-
5
- ℹ tsdown v0.20.3 powered by rolldown v1.0.0-rc.3
6
- ℹ config file: /home/runner/work/better-auth/better-auth/packages/memory-adapter/tsdown.config.ts
7
- ℹ entry: src/index.ts
8
- ℹ tsconfig: tsconfig.json
9
- ℹ Build start
10
- ℹ dist/index.mjs  8.33 kB │ gzip: 2.29 kB
11
- ℹ dist/index.mjs.map 18.21 kB │ gzip: 4.79 kB
12
- ℹ dist/index.d.mts  0.67 kB │ gzip: 0.32 kB
13
- ℹ 3 files, total: 27.21 kB
14
- [PLUGIN_TIMINGS] Warning: Your build spent significant time in plugin `rolldown-plugin-dts:generate`. See https://rolldown.rs/options/checks#plugintimings for more details.
15
-
16
- ✔ Build complete in 8781ms
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export {
2
- type MemoryAdapterConfig,
3
- type MemoryDB,
4
- memoryAdapter,
5
- } from "./memory-adapter";
@@ -1,370 +0,0 @@
1
- import type { BetterAuthOptions } from "@better-auth/core";
2
- import type {
3
- CleanedWhere,
4
- DBAdapterDebugLogOption,
5
- JoinConfig,
6
- } from "@better-auth/core/db/adapter";
7
- import { createAdapterFactory } from "@better-auth/core/db/adapter";
8
- import { logger } from "@better-auth/core/env";
9
-
10
- export interface MemoryDB {
11
- [key: string]: any[];
12
- }
13
-
14
- export interface MemoryAdapterConfig {
15
- debugLogs?: DBAdapterDebugLogOption | undefined;
16
- }
17
-
18
- export const memoryAdapter = (
19
- db: MemoryDB,
20
- config?: MemoryAdapterConfig | undefined,
21
- ) => {
22
- let lazyOptions: BetterAuthOptions | null = null;
23
- const adapterCreator = createAdapterFactory({
24
- config: {
25
- adapterId: "memory",
26
- adapterName: "Memory Adapter",
27
- usePlural: false,
28
- debugLogs: config?.debugLogs || false,
29
- supportsArrays: true,
30
- customTransformInput(props) {
31
- const useNumberId =
32
- props.options.advanced?.database?.generateId === "serial";
33
- if (useNumberId && props.field === "id" && props.action === "create") {
34
- return db[props.model]!.length + 1;
35
- }
36
- return props.data;
37
- },
38
- transaction: async (cb) => {
39
- const clone = structuredClone(db);
40
- try {
41
- const r = await cb(adapterCreator(lazyOptions!));
42
- return r;
43
- } catch (error) {
44
- // Rollback changes
45
- Object.keys(db).forEach((key) => {
46
- db[key] = clone[key]!;
47
- });
48
- throw error;
49
- }
50
- },
51
- },
52
- adapter: ({ getFieldName, getDefaultFieldName, options, getModelName }) => {
53
- const applySortToRecords = (
54
- records: any[],
55
- sortBy: { field: string; direction: "asc" | "desc" } | undefined,
56
- model: string,
57
- ) => {
58
- if (!sortBy) return records;
59
- return records.sort((a: any, b: any) => {
60
- const field = getFieldName({ model, field: sortBy.field });
61
- const aValue = a[field];
62
- const bValue = b[field];
63
-
64
- let comparison = 0;
65
-
66
- // Handle null/undefined values
67
- if (aValue == null && bValue == null) {
68
- comparison = 0;
69
- } else if (aValue == null) {
70
- comparison = -1;
71
- } else if (bValue == null) {
72
- comparison = 1;
73
- }
74
- // Handle string comparison
75
- else if (typeof aValue === "string" && typeof bValue === "string") {
76
- comparison = aValue.localeCompare(bValue);
77
- }
78
- // Handle date comparison
79
- else if (aValue instanceof Date && bValue instanceof Date) {
80
- comparison = aValue.getTime() - bValue.getTime();
81
- }
82
- // Handle numeric comparison
83
- else if (typeof aValue === "number" && typeof bValue === "number") {
84
- comparison = aValue - bValue;
85
- }
86
- // Handle boolean comparison
87
- else if (typeof aValue === "boolean" && typeof bValue === "boolean") {
88
- comparison = aValue === bValue ? 0 : aValue ? 1 : -1;
89
- }
90
- // Fallback to string comparison
91
- else {
92
- comparison = String(aValue).localeCompare(String(bValue));
93
- }
94
-
95
- return sortBy.direction === "asc" ? comparison : -comparison;
96
- });
97
- };
98
-
99
- function convertWhereClause(
100
- where: CleanedWhere[],
101
- model: string,
102
- join?: JoinConfig,
103
- select?: string[],
104
- ): any[] {
105
- const baseRecords = (() => {
106
- const table = db[model];
107
- if (!table) {
108
- logger.error(
109
- `[MemoryAdapter] Model ${model} not found in the DB`,
110
- Object.keys(db),
111
- );
112
- throw new Error(`Model ${model} not found`);
113
- }
114
-
115
- const evalClause = (record: any, clause: CleanedWhere): boolean => {
116
- const { field, value, operator } = clause;
117
- switch (operator) {
118
- case "in":
119
- if (!Array.isArray(value)) {
120
- throw new Error("Value must be an array");
121
- }
122
- // @ts-expect-error
123
- return value.includes(record[field]);
124
- case "not_in":
125
- if (!Array.isArray(value)) {
126
- throw new Error("Value must be an array");
127
- }
128
- // @ts-expect-error
129
- return !value.includes(record[field]);
130
- case "contains":
131
- return record[field].includes(value);
132
- case "starts_with":
133
- return record[field].startsWith(value);
134
- case "ends_with":
135
- return record[field].endsWith(value);
136
- case "ne":
137
- return record[field] !== value;
138
- case "gt":
139
- return value != null && Boolean(record[field] > value);
140
- case "gte":
141
- return value != null && Boolean(record[field] >= value);
142
- case "lt":
143
- return value != null && Boolean(record[field] < value);
144
- case "lte":
145
- return value != null && Boolean(record[field] <= value);
146
- default:
147
- return record[field] === value;
148
- }
149
- };
150
-
151
- let records = table.filter((record: any) => {
152
- if (!where.length || where.length === 0) {
153
- return true;
154
- }
155
-
156
- let result = evalClause(record, where[0]!);
157
- for (const clause of where) {
158
- const clauseResult = evalClause(record, clause);
159
-
160
- if (clause.connector === "OR") {
161
- result = result || clauseResult;
162
- } else {
163
- result = result && clauseResult;
164
- }
165
- }
166
-
167
- return result;
168
- });
169
- if (select?.length && select.length > 0) {
170
- records = records.map((record: any) =>
171
- Object.fromEntries(
172
- Object.entries(record).filter(([key]) =>
173
- select.includes(getDefaultFieldName({ model, field: key })),
174
- ),
175
- ),
176
- );
177
- }
178
- return records;
179
- })();
180
-
181
- if (!join) return baseRecords;
182
-
183
- // Group results by base model and nest joined data as arrays
184
- const grouped = new Map<string, any>();
185
- // Track seen IDs per joined model for O(1) deduplication
186
- const seenIds = new Map<string, Set<string>>();
187
-
188
- for (const baseRecord of baseRecords) {
189
- const baseId = String(baseRecord.id);
190
-
191
- if (!grouped.has(baseId)) {
192
- const nested: Record<string, any> = { ...baseRecord };
193
-
194
- // Initialize joined data structures based on isUnique
195
- for (const [joinModel, joinAttr] of Object.entries(join)) {
196
- const joinModelName = getModelName(joinModel);
197
- if (joinAttr.relation === "one-to-one") {
198
- nested[joinModelName] = null;
199
- } else {
200
- nested[joinModelName] = [];
201
- seenIds.set(`${baseId}-${joinModel}`, new Set());
202
- }
203
- }
204
-
205
- grouped.set(baseId, nested);
206
- }
207
-
208
- const nestedEntry = grouped.get(baseId)!;
209
-
210
- // Add joined data
211
- for (const [joinModel, joinAttr] of Object.entries(join)) {
212
- const joinModelName = getModelName(joinModel);
213
- const joinTable = db[joinModelName];
214
- if (!joinTable) {
215
- logger.error(
216
- `[MemoryAdapter] JoinOption model ${joinModelName} not found in the DB`,
217
- Object.keys(db),
218
- );
219
- throw new Error(`JoinOption model ${joinModelName} not found`);
220
- }
221
-
222
- const matchingRecords = joinTable.filter(
223
- (joinRecord: any) =>
224
- joinRecord[joinAttr.on.to] === baseRecord[joinAttr.on.from],
225
- );
226
-
227
- if (joinAttr.relation === "one-to-one") {
228
- // For unique relationships, store a single object (or null)
229
- nestedEntry[joinModelName] = matchingRecords[0] || null;
230
- } else {
231
- // For non-unique relationships, store array with limit
232
- const seenSet = seenIds.get(`${baseId}-${joinModel}`)!;
233
- const limit = joinAttr.limit ?? 100;
234
- let count = 0;
235
-
236
- for (const matchingRecord of matchingRecords) {
237
- if (count >= limit) break;
238
- if (!seenSet.has(matchingRecord.id)) {
239
- nestedEntry[joinModelName].push(matchingRecord);
240
- seenSet.add(matchingRecord.id);
241
- count++;
242
- }
243
- }
244
- }
245
- }
246
- }
247
-
248
- return Array.from(grouped.values());
249
- }
250
- return {
251
- create: async ({ model, data }) => {
252
- const useNumberId =
253
- options.advanced?.database?.generateId === "serial";
254
- if (useNumberId) {
255
- // @ts-expect-error
256
- data.id = db[getModelName(model)]!.length + 1;
257
- }
258
- if (!db[model]) {
259
- db[model] = [];
260
- }
261
- db[model]!.push(data);
262
- return data;
263
- },
264
- findOne: async ({ model, where, select, join }) => {
265
- const res = convertWhereClause(where, model, join, select);
266
- if (join) {
267
- // When join is present, res is an array of nested objects
268
- const resArray = res as any[];
269
- if (!resArray.length) {
270
- return null;
271
- }
272
- // Return the first nested object
273
- return resArray[0];
274
- }
275
- // Without join, res is an array
276
- const resArray = res as any[];
277
- const record = resArray[0] || null;
278
- return record;
279
- },
280
- findMany: async ({
281
- model,
282
- where,
283
- sortBy,
284
- limit,
285
- select,
286
- offset,
287
- join,
288
- }) => {
289
- const res = convertWhereClause(where || [], model, join, select);
290
-
291
- if (join) {
292
- // When join is present, res is an array of nested objects
293
- const resArray = res as any[];
294
- if (!resArray.length) {
295
- return [];
296
- }
297
-
298
- // Apply sorting to nested objects
299
- applySortToRecords(resArray, sortBy, model);
300
-
301
- // Apply offset and limit
302
- let paginatedRecords = resArray;
303
- if (offset !== undefined) {
304
- paginatedRecords = paginatedRecords.slice(offset);
305
- }
306
- if (limit !== undefined) {
307
- paginatedRecords = paginatedRecords.slice(0, limit);
308
- }
309
-
310
- return paginatedRecords;
311
- }
312
-
313
- // Without join - original logic
314
- const resArray = res as any[];
315
- let table = applySortToRecords(resArray, sortBy, model);
316
- if (offset !== undefined) {
317
- table = table!.slice(offset);
318
- }
319
- if (limit !== undefined) {
320
- table = table!.slice(0, limit);
321
- }
322
- return table || [];
323
- },
324
- count: async ({ model, where }) => {
325
- if (where) {
326
- const filteredRecords = convertWhereClause(where, model);
327
- return filteredRecords.length;
328
- }
329
- return db[model]!.length;
330
- },
331
- update: async ({ model, where, update }) => {
332
- const res = convertWhereClause(where, model);
333
- res.forEach((record) => {
334
- Object.assign(record, update);
335
- });
336
- return res[0] || null;
337
- },
338
- delete: async ({ model, where }) => {
339
- const table = db[model]!;
340
- const res = convertWhereClause(where, model);
341
- db[model] = table.filter((record) => !res.includes(record));
342
- },
343
- deleteMany: async ({ model, where }) => {
344
- const table = db[model]!;
345
- const res = convertWhereClause(where, model);
346
- let count = 0;
347
- db[model] = table.filter((record) => {
348
- if (res.includes(record)) {
349
- count++;
350
- return false;
351
- }
352
- return !res.includes(record);
353
- });
354
- return count;
355
- },
356
- updateMany({ model, where, update }) {
357
- const res = convertWhereClause(where, model);
358
- res.forEach((record) => {
359
- Object.assign(record, update);
360
- });
361
- return res[0] || null;
362
- },
363
- };
364
- },
365
- });
366
- return (options: BetterAuthOptions) => {
367
- lazyOptions = options;
368
- return adapterCreator(options);
369
- };
370
- };
package/tsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "include": ["./src"],
4
- "references": [
5
- {
6
- "path": "../core/tsconfig.json"
7
- }
8
- ]
9
- }
package/tsdown.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineConfig } from "tsdown";
2
-
3
- export default defineConfig({
4
- dts: { build: true, incremental: true },
5
- format: ["esm"],
6
- entry: ["./src/index.ts"],
7
- sourcemap: true,
8
- });
package/vitest.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineProject } from "vitest/config";
2
-
3
- export default defineProject({
4
- test: {
5
- clearMocks: true,
6
- restoreMocks: true,
7
- },
8
- });