深度解析:开发、测试与事务隔离实践)
Better Auth 内存适配器memory-adapter深度解析开发、测试与事务隔离实践【免费下载链接】better-authThe most comprehensive authentication framework项目地址: https://gitcode.com/GitHub_Trending/be/better-authBetter Auth 的better-auth/memory-adapter是一款将认证数据保存在进程内 JavaScript 对象中的内存适配器专为开发调试、单元测试与原型验证场景设计。本文以该适配器的 CHANGELOG.md 中记录的能力演进为主线结合 memory-adapter.ts 源码与 memory-adapter.test.ts 测试用例完整讲解它的安装配置、查询语法、写入语义、copy-on-write 事务隔离以及计数原子化、空谓词防护等关键改进的底层实现读完即可在本地项目里独立搭建一套零依赖的认证存储环境。一、内存适配器是什么解决什么问题Better Auth 通过适配器Adapter抽象层把认证数据存储与核心逻辑解耦。memory-adapter是这套抽象中的内存实现所有数据存放在一个普通的{ [model]: any[] }对象里不依赖任何数据库服务。从 README.md 的定位来看它明确适用于开发和测试场景适合本地开发时快速启动认证功能无需安装 PostgreSQL、MySQL 或 MongoDB编写单元测试/集成测试时获得确定性的数据快照避免清理数据库的繁琐步骤在 CI 环境或演示Demo项目中验证认证流程是否正常。需要强调的是从源码注释可以推断它面向的是开发与测试而非生产并发控制内存适配器不会串行化写入同一行的并发修改采用最后写入者获胜last-writer-wins策略隔离性只保证到行/表粒度。二、安装与最小接入在项目中使用 npm 安装npm install better-auth/memory-adapter该包以 ESM 形式发布入口为dist/index.mjs同时通过dev-source字段在 Better Auth monorepo 内部直接指向 src/index.ts。从 package.json 可以看到它声明了对better-auth/core的 peer 依赖因此安装时需保证 Better Auth 核心版本匹配。最小接入方式如下import { betterAuth } from better-auth; import { memoryAdapter } from better-auth/memory-adapter; // 内存数据库键为模型名值为行记录数组 const db { user: [], session: [], account: [], verification: [], }; export const auth betterAuth({ database: memoryAdapter(db), });memoryAdapter(db, config?)接收两个参数参数类型说明dbMemoryDB即{ [key: string]: any[] }直接传入的对象会被原地修改外部持有的引用始终有效memory-adapter.tsconfigMemoryAdapterConfig可选配置目前仅支持debugLogs用于开启/关闭适配器调试日志默认false调用后返回一个函数接收BetterAuthOptions并产出适配器实例因此也可以延迟到创建betterAuth时再传入完整选项。三、核心读写能力与查询语义内存适配器通过createAdapterFactory构建其adapterId为memory、adapterName为Memory Adapter并使用usePlural: false、supportsArrays: truememory-adapter.ts。它实现了 Better Auth 适配器接口下的全部核心操作create、findOne、findMany、count、update、delete、deleteMany、updateMany、consumeOne、incrementOne以及transaction。3.1 查询过滤器Where的运算符convertWhereClause是查询语义的核心实现memory-adapter.ts支持以下运算符运算符语义特殊说明eq默认严格相等value null时按 SQLIS NULL语义匹配record[field] null即undefined与null等价memory-adapter.tsne不等in/not_in属于 / 不属于数组要求 value 为数组否则抛错contains包含子串记录字段需为字符串starts_with/ends_with前缀 / 后缀匹配gt/gte/lt/lte数值/日期范围比较value 为null时直接返回 false多个子句之间通过connector连接缺省为AND显式标记OR的子句执行或逻辑memory-adapter.ts。3.2 大小写不敏感查询1.6.0 新增能力CHANGELOG 在1.6.0版本记录了一项 Minor ChangeAdd case-insensitive query support for database adaptersPR #8836。在内存适配器中这一能力通过 query-builders.ts 中的一组辅助函数实现insensitiveCompare / insensitiveIn / insensitiveNotIn / insensitiveContains / insensitiveStartsWith / insensitiveEndsWith使用方式是在 where 子句中指定mode: insensitive。判定逻辑位于 memory-adapter.ts只有当mode insensitive且值是字符串或全为字符串的数组时才走不敏感比较非字符串值退回严格比较。例如// 大小写不敏感地查找邮箱 const user await adapter.findOne({ model: user, where: [ { field: email, value: AliceExample.com, mode: insensitive }, ], });3.3 排序、分页与字段选择findMany支持sortBy、offset、limit与select排序applySortToRecordsmemory-adapter.ts对 null/undefined、字符串localeCompare、Date毫秒差、数字、布尔值分别处理asc/desc通过比较结果取反实现分页先按 offset 切片再按 limit 截取字段投影select会基于默认字段名过滤每条记录的键memory-adapter.ts。3.4 关联查询Join当传入join配置时适配器以基础模型记录为分组单元把关联表记录嵌套进结果one-to-one关系存单个对象或null非唯一关系存数组并通过limit默认 100与按id的 Set 去重来控制关联条数memory-adapter.ts。findOne在带 join 时返回第一个嵌套对象否则返回数组首条。四、写入语义与 CHANGELOG 中的关键改进4.1 空谓词防护单数写操作不再全表修改在1.6.17的 Patch Changes 中记录了一项重要行为修正A singularupdateordeletecalled with an empty filter is now a no-op instead of changing every row。对应源码中update与delete在where.length 0时直接返回null/undefined注释明确说明匹配全部语义只保留给updateMany/deleteMany单数写操作绝不允许误改所有行memory-adapter.ts。测试用例memory adapter singular mutation with empty predicate验证了空谓词update返回null且两行数据原封不动memory-adapter.test.ts。4.2 updateMany 返回受影响行数同一条目还记录updateManyreturns the number of rows it affected。源码实现先执行查询再对每条命中的记录Object.assign合并更新字段最终返回命中记录数而非记录本身memory-adapter.ts。测试覆盖了 0 命中、多行命中与空谓词全表三种情况分别返回0、2、3memory-adapter.test.ts。4.3 计数器原子更新incrementOne 与 consumeOne1.6.17的另一项关键改进是Counter updates on the memory, Kysely, Drizzle, Prisma, and MongoDB adapters are now atomic on the default configuration... Each adapter implementsincrementOnenatively as a single statement。这条改动直接影响限流rate limiting与 API Key 用量计数Better Auth 的 API Key 插件正是通过这类计数操作实现速率限制与用量上限见 api-key 包测试 中的限流用例。内存适配器的incrementOne实现memory-adapter.tsincrementOne: async ({ model, where, increment, set }) { const target convertWhereClause(where, model)[0]; if (!target) return null; for (const [field, delta] of Object.entries(increment)) { const current typeof target[field] number ? target[field] : 0; target[field] current delta; } if (set) Object.assign(target, set); return target; }行为要点均有测试覆盖计数缺失时按0起算count: 5加 3 得 8缺失字段加 4 得 4支持负数递减remaining: -1可同时携带set做绝对赋值仅更新首个命中行未命中返回null且不做任何修改参与事务隔离事务失败时增量被整体丢弃。consumeOnememory-adapter.ts则实现取走一条并删除的消费语义找到首个匹配行后从表中移除并返回它未命中返回null适用于一次性令牌、验证码等场景。五、事务copy-on-write 隔离与三方合并CHANGELOG 1.6.17 同时修复了内存适配器的事务并发问题A failed transaction on the memory adapter no longer discards writes made by other operations running at the same time。要理解这个修复需要看事务的完整实现。5.1 两阶段 copy-on-writetransaction回调memory-adapter.ts的执行过程快照structuredClone复制activeDb得到base与clone隔离执行在clone之上构建一个事务专用适配器所有未提交写入只落在clone上对实时activeDb不可见提交成功后调用mergeTransactionInto(activeDb, base, clone)把base - clone的差异回放replay到实时库失败clone被直接丢弃实时库完全不受影响。测试a failing transaction must not erase a write made by a concurrent in-flight operation用 Promise 门闩制造事务进行中、外部并发写入落地的交错场景验证回滚后外部写入依然保留memory-adapter.test.ts。5.2 三方合并three-way merge保证并发安全为什么需要base、clone、target三份数据mergeTransactionInto的注释给出了答案memory-adapter.tsbase事务开始时的快照clone事务修改后的快照target当前实时库可能已包含并发写入。合并时只回放base - clone的增量因此事务未触碰的行保留实时版本并发对其它行的修改安然无恙事务修改过的行以事务版本为准同一行的并发编辑采用 last-writer-wins事务新建的行按插入顺序追加事务整表删除的模型在实时库中同步删除。rowChanged通过JSON.stringify比较base与clone中的行判断是否被事务改动memory-adapter.ts。测试a committing transaction must not erase a write made by a concurrent in-flight operation验证了提交路径同样保留并发写入。这套机制的实际价值是内存适配器虽然不做写串行化但通过只回放自己的增量这一约束避免了一次失败/成功的事务覆盖掉其它操作在 await 间隙插入的数据在开发与测试场景下提供了足够可靠的语义保证。六、进阶实践serial 主键与调试6.1 自增数字主键当 Better Auth 配置了advanced.database.generateId serial时适配器会在create时自动生成数字主键activeDb[model].length 1该逻辑同时出现在配置层的customTransformInput与适配器层的create中memory-adapter.ts。反之默认情况下记录直接携带外部传入的idjoin 逻辑也正是以record.id作为行级身份标识indexByIdmemory-adapter.ts。6.2 调试日志debugLogs透传给适配器工厂的config.debugLogs可用于观察每个操作的执行细节。当查询的模型在库中不存在时适配器会通过logger.error输出可用模型列表并抛出Model not found错误memory-adapter.ts这有助于快速定位模型名拼写或插件 schema 未注册的问题。七、使用建议与边界综合 CHANGELOG 的能力演进与源码实现给出以下实践建议开发与测试首选内存适配器让betterAuth可在零外部依赖下运行适合作为本地调试与测试基线生产环境请切换到 drizzle-adapter / prisma-adapter 等持久化实现。验证计数与限流逻辑利用incrementOne的原子语义与测试覆盖可以可靠地模拟 API Key 用量、限流窗口等计数场景对应 api-key 插件 的运行前提。事务语义边界事务隔离只到行/表粒度同行的并发写采用 last-writer-wins不能当作分布式事务或强并发控制来用。版本跟随CHANGELOG 显示该包长期与better-auth/core同步发布1.6.x ~ 1.7.x升级时请保证两者版本对齐避免 peer 依赖不匹配。八、小结better-auth/memory-adapter虽然是一个为开发与测试而生的轻量实现但在查询能力上并不缩水完整的 where 运算符、大小写不敏感匹配、排序分页、关联嵌套、原子计数以及带三方合并的 copy-on-write 事务构成了一个功能完备的认证存储沙箱。CHANGELOG 中 1.6.0 与 1.6.17 两次实质性变更不敏感查询、原子计数、空谓词防护、并发安全事务在 memory-adapter.ts 与 memory-adapter.test.ts 中均有完整的实现与测试佐证是理解 Better Auth 适配器抽象层行为约定的一份高质量参考样本。【免费下载链接】better-authThe most comprehensive authentication framework项目地址: https://gitcode.com/GitHub_Trending/be/better-auth创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考