drizzle-orm 0.44.5 版本解析:durable-sqlite.one()修复与 SQLite blob 列的跨环境支持强化
【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm
本篇技术指南围绕 drizzle-orm 0.44.5 发布说明(changelogs/drizzle-orm/0.44.5.md)中的四项修复展开,深入讲解 Durable Objects SQLite 驱动下.one()查询方法的正确用法,以及 SQLiteblob列在 Node.js、浏览器等不同运行时下的取值映射机制。读完本文,你将理解这四条修复背后的底层实现,掌握blob列三种模式(buffer/json/bigint)的选型原则,并能安全升级到该版本。
版本概览:一次聚焦于 SQLite 稳定性与兼容性的小版本
drizzle-orm 0.44.5 是紧随 0.44.4(该版本修复了DrizzleQueryError导出问题,见 changelogs/drizzle-orm/0.44.4.md)之后的一个补丁级版本,共包含四项变更,全部集中在 SQLite 相关能力上:
- 修复
durable-sqlitesession 中.one()的无效用法(invalid usage); - 修复 SQLite
blob列在 spread(展开)运算符场景下的崩溃问题; - 提升 SQLite
blob列在浏览器环境中的支持; - 改进 SQLite
blob列的值映射(mapping)逻辑。
四项变更中三项都围绕blob列,可见该版本的核心主题是"让blob列在不同运行时与不同输入形态下都能稳定工作",同时兼顾了 Cloudflare Durable Objects 上 SQLite 查询体验的一致性。
durable-sqlite session 的.one()修复:理解 execute 方法分发机制
修复背景:.one()与 SQLite execute 方法的对应关系
在 drizzle-orm 的查询构建体系中,SQLite 的预编译查询支持四种执行方法,定义在 drizzle-orm/src/sqlite-core/session.ts:
export type SQLiteExecuteMethod = 'run' | 'all' | 'get';而SQLitePreparedQuery基类的execute()会根据executeMethod动态分发到对应实现(见 sqlite-core/session.ts):
execute(placeholderValues?: Record<string, unknown>): ExecuteResult<T['type'], T['execute']> { return thisthis.executeMethod as ExecuteResult<T['type'], T['execute']>; }查询构建器上的.one()方法约定语义为"恰好返回一条记录",在底层会映射为executeMethod === 'get',最终调用预编译查询对象的get()方法。因此,session 的get()实现是否正确、是否符合基类契约,直接决定了.one()能否正常工作。
修复内容:SQLiteDOPreparedQuery.get()的取值方式
在 drizzle-orm/src/durable-sqlite/session.ts 中,SQLiteDOPreparedQuery的get()实现如下:
get(placeholderValues?: Record<string, unknown>): T['get'] { const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); this.logger.logQuery(this.query.sql, params); const { fields, client, joinsNotNullableMap, customResultMapper, query } = this; if (!fields && !customResultMapper) { return (params.length > 0 ? client.sql.exec(query.sql, ...params) : client.sql.exec(query.sql)).next().value; } const rows = this.values(placeholderValues) as unknown[][]; const row = rows[0]; if (!row) { return undefined; } if (customResultMapper) { return customResultMapper(rows) as T['get']; } return mapResultRow(fields!, row, joinsNotNullableMap); }该实现遵循了 SQLite 同步驱动('sync'类型)的取值契约:无字段映射时直接通过client.sql.exec(...)执行 SQL,并取结果集的next().value作为单行;有字段映射(如带fields的联表查询或自定义结果映射器)时则走values()拿到原始行数组,再取首行。旧版本中.one()在此 session 下无法正常工作,很可能与get()对无字段/有字段两种路径的处理不完整有关;0.44.5 将其修正为与 durable-sqlite/driver.ts 中drizzle()入口所创建的SQLiteDOSession(见 durable-sqlite/session.ts)一致的完整语义。
实际影响与验证方式
修复后,在 Cloudflare Durable Objects 中使用 SQLite(通过DurableObjectStorage的sqlAPI)时,db.select().from(table).where(...).one()这类查询将稳定返回单条记录(无匹配时返回undefined)。从源码结构看,SQLiteDOPreparedQuery的run()(session.ts)、all()(session.ts)、values()(session.ts)分别对应写操作、多行查询与原始值数组查询,三者此前不受影响,本次修复补齐的是.one()这条单行取值链路。
修复 spread 运算符导致的 blob 崩溃:输入形态归一化
问题本质:驱动返回的二进制对象形态不一
SQLite 的blob列在各驱动与运行时下,读回的值可能是Buffer、Uint8Array或ArrayBuffer中的任意一种。若列映射代码以"展开"(spread)方式处理这些二进制对象(例如对Uint8Array执行Buffer.from(...value)或对类数组结构做展开),在不同形态之间切换时就会触发运行时崩溃——这正是 0.44.5 修复的第二项问题。
修复方式:在mapFromDriverValue中统一转换为 Buffer
查看 drizzle-orm/src/sqlite-core/columns/blob.ts,默认(buffer模式)的取值映射已经改为基于类型判断的安全转换:
override mapFromDriverValue(value: Buffer | Uint8Array | ArrayBuffer): T['data'] { if (Buffer.isBuffer(value)) { return value; } return Buffer.from(value as Uint8Array); }不再对二进制对象做逐元素展开,而是直接通过Buffer.from()整体拷贝,从根源上消除了 spread 运算符在二进制对象上可能引发的崩溃。从代码结构可以推断,此前版本可能对Uint8Array/ArrayBuffer使用了展开或逐个元素拷贝的写法,遇到非 Buffer 输入时就会出错;现在所有输入都被归一化为Buffer,后续逻辑只依赖一种稳定形态。
浏览器环境下的 blob 支持:textDecoder兜底路径
问题背景:浏览器没有全局 Buffer
SQLite 的blob映射逻辑(尤其是json与bigint两种需要把字节解码为文本的模式)在 Node.js 中依赖全局Buffer。但在浏览器(如 D1 HTTP 调用、libsql wasm、浏览器内运行的 SQLite 场景)中,全局Buffer并不存在,直接调用Buffer.from(...)会抛错。0.44.5 的"Better browser support"即针对此问题。
源码实现:环境探测 + 双路径解码
以json模式(SQLiteBlobJson)的取值为例,blob.ts 的实现为:
override mapFromDriverValue(value: Buffer | Uint8Array | ArrayBuffer): T['data'] { if (typeof Buffer !== 'undefined' && Buffer.from) { const buf = Buffer.isBuffer(value) ? value // eslint-disable-next-line no-instanceof/no-instanceof : value instanceof ArrayBuffer ? Buffer.from(value) : value.buffer ? Buffer.from(value.buffer, value.byteOffset, value.byteLength) : Buffer.from(value); return JSON.parse(buf.toString('utf8')); } return JSON.parse(textDecoder!.decode(value)); }关键点有三:
- 环境探测:先判断
typeof Buffer !== 'undefined' && Buffer.from,存在才走 Node 路径; - 形态归一:Node 路径下再区分
Buffer、ArrayBuffer、带buffer视图的Uint8Array(注意通过byteOffset/byteLength精确切分子视图),最终统一为Buffer; - 兜底解码:浏览器环境回退到
textDecoder.decode(value)——该textDecoder从 drizzle-orm/src/utils.ts 引入(textDecoder常量,见 blob.ts 第 5 行的导入),同样是bigint模式(SQLiteBigInt,blob.ts)的兜底方案。
这一"先探测、再归一、终兜底"的三层结构,正是该版本把 blob 支持扩展到浏览器运行时的实现基础。
blob 映射的全面改进:三种模式选型指南
blob()工厂函数与三种模式
blob()是 SQLite 列构造器,定义于 drizzle-orm/src/sqlite-core/columns/blob.ts,根据配置返回不同的列类型:
export function blob(): SQLiteBlobJsonBuilderInitial<''>; export function blob<TMode extends BlobMode = BlobMode>( config?: BlobConfig<TMode>, ): Equal<TMode, 'bigint'> extends true ? SQLiteBigIntBuilderInitial<''> : Equal<TMode, 'buffer'> extends true ? SQLiteBlobBufferBuilderInitial<''> : SQLiteBlobJsonBuilderInitial<''>; // ... 带列名的重载 ... export function blob(a?: string | BlobConfig, b?: BlobConfig) { const { name, config } = getColumnNameAndConfig<BlobConfig | undefined>(a, b); if (config?.mode === 'json') { return new SQLiteBlobJsonBuilder(name); } if (config?.mode === 'bigint') { return new SQLiteBigIntBuilder(name); } return new SQLiteBlobBufferBuilder(name); }三种模式(BlobMode,见 blob.ts)对比如下:
| 模式 | 列类型 | TypeScript 数据类型 | 驱动参数类型 | 写入映射 | 读取映射 |
|---|---|---|---|---|---|
buffer(默认) | SQLiteBlobBuffer | Buffer | Buffer | 原样传递 | Buffer.isBuffer(value) ? value : Buffer.from(value) |
json | SQLiteBlobJson | unknown(JSON 值) | Buffer | Buffer.from(JSON.stringify(value)) | JSON.parse(buf.toString('utf8')) |
bigint | SQLiteBigInt | bigint | Buffer | Buffer.from(value.toString()) | BigInt(buf.toString('utf8')) |
使用建议
- 默认不传
mode时得到buffer模式,适合存储原始二进制数据(文件内容、哈希值等); - 需要把对象/数组以 JSON 形式存入 blob 时使用
blob('data', { mode: 'json' })。不过官方在 blob.ts 的注释中明确提示:推荐优先使用text('...', { mode: 'json' })替代 JSON 模式的 blob,因为 SQLite 的 JSON 函数会对 BLOB 参数抛错(BLOB 被保留用于未来 JSON 的二进制编码,见 SQLite json1 文档说明); - 需要把
bigint存入 blob(SQLite 本身无原生 bigint 类型)时使用blob('id', { mode: 'bigint' }),写入时按 UTF-8 编码数字字符串,读取时通过BigInt()还原。
本次映射改进的要点
0.44.5 对映射逻辑的改进主要体现在"输入归一化"与"视图精确处理"两点:所有模式的mapFromDriverValue都接受Buffer | Uint8Array | ArrayBuffer三种输入;对于Uint8Array子视图(value.buffer存在且偏移非零的情况),通过Buffer.from(value.buffer, value.byteOffset, value.byteLength)精确拷贝,避免因偏移量导致数据错位。这些改动共同保证了不同驱动、不同运行时下 blob 读写结果的一致性。
升级建议与验证路径
0.44.5 是纯修复性质的补丁版本,不包含破坏性变更,可以放心升级:
- 若你使用了 durable-sqlite 驱动(Cloudflare Durable Objects 上的 SQLite),升级后重点回归
select ... .one()单行查询路径,确认返回单条记录或undefined,且不再报错; - 若你使用了 blob 列,升级后请在目标运行时(Node.js 或浏览器环境)分别执行读写冒烟测试,覆盖三种模式(
buffer/json/bigint),并特别验证Uint8Array子视图数据(如bytes.subarray(...)的读取结果)与 JSON/bigint 文本解码是否正确; - 相关实现均可直接查阅源码继续深入:durable-sqlite 会话与查询在 drizzle-orm/src/durable-sqlite/session.ts,入口在 drizzle-orm/src/durable-sqlite/driver.ts;blob 列三种模式的完整实现与文档注释在 drizzle-orm/src/sqlite-core/columns/blob.ts。
结语
drizzle-orm 0.44.5 是一个典型的"小版本大修内功"的补丁:修复了 durable-sqlite 上.one()查询的可用性问题,并为 SQLiteblob列建立了"环境探测 → 输入归一 → 文本兜底"的三层取值管线,使其在 Node 与浏览器环境下都能稳定映射buffer/json/bigint三种模式。对于在 Cloudflare Durable Objects、浏览器端 SQLite 场景中使用 drizzle-orm 的开发者,这是一个值得关注的稳定性更新。
【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考