使用 @tursodatabase/database 在 Node.js 中运行 Turso 嵌入式数据库:官方示例 database-node 全解析
2026/9/13 20:14:56 网站建设 项目流程

使用 @tursodatabase/database 在 Node.js 中运行 Turso 嵌入式数据库:官方示例 database-node 全解析

【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso

本文以 Turso 仓库中官方最小示例 database-node 为主线,系统讲解如何在 Node.js 应用中通过@tursodatabase/database包创建本地文件型数据库、执行建表与索引 DDL、使用预处理语句绑定参数完成写入与查询。读完本文,你将掌握connect / exec / prepare / run / all / get的完整用法,理解timeout等连接选项的底层语义,并能对照源码定位每个 API 的实现位置,快速上手基于 Turso 嵌入式引擎的应用开发。

示例项目概览:一个只依赖数据库包的 Node 最小工程

官方示例 database-node 目录结构非常精简,共 4 个文件:

  • README.md:使用说明(即本文讲解的关联文档);
  • index.mjs:全部演示逻辑,以 ES Module 方式编写;
  • package.json:声明唯一运行时依赖@tursodatabase/database
  • package-lock.json:锁定依赖版本。

从 package.json 可以看到,示例通过本地路径../../../bindings/javascript/packages/native直接链接到仓库内的 native 包,也就是说示例代码与你 clone 下来的源码树实时对应,不会出现版本漂移。该包在 bindings/javascript/packages/native/package.json 中发布名为@tursodatabase/database,当前仓库锁定版本为0.8.0-pre.11,license 为 MIT,exports同时提供./dist/promise.js主入口与./compat兼容入口。

npm install node index.mjs

这两条命令就是 README 给出的全部运行步骤:先安装依赖,再直接以 Node.js 运行index.mjs。由于示例没有第三方工具链依赖,安装后即可执行。

连接数据库:connect()timeout选项

示例的第一行代码从包中导入connect并打开本地数据库文件:

import { connect } from "@tursodatabase/database"; const db = await connect("local.db", { timeout: 1000, // busy timeout for handling high-concurrency write cases });

connect的签名在 promise.ts 中有明确定义:

async function connect(path: string, opts: DatabaseOpts = {}): Promise<Database> { const db = new Database(path, opts); await db.connect(); return db; }

它内部构造Database实例后异步调用connect(),因此返回的是一个Promise<Database>,这也是示例中所有调用都需要await的原因。path传普通文件路径即创建/打开文件型数据库;传":memory:"则创建纯内存数据库(native 包 README 与 promise.test.ts 中的in-memory-db-async测试均验证了该用法)。

DatabaseOpts支持哪些选项

完整的连接选项定义在 index.d.ts(该文件由 NAPI-RS 自动生成,与 better-sqlite3 的选项对齐):

选项类型含义
readonlyboolean只读模式打开数据库
timeoutnumberbusy timeout,单位毫秒,用于高并发写场景下等待锁释放
defaultQueryTimeoutnumber语句默认查询超时
fileMustExistboolean文件不存在时报错而非自动创建
tracingstring语句追踪配置
experimentalstring[]需要开启的实验特性列表
encryptionEncryptionOpts本地数据库加密配置(cipher + hexkey)

示例使用的timeout: 1000正是 busy timeout:当多个连接并发写入、目标连接持锁未释放时,当前连接会重试等待,最长等待 1000ms。这一语义在 promise.test.ts 中有专门的测试佐证:conn1开启事务写锁后不提交,conn2timeout: 200写入时,会在约 200ms 预算内不断重试,随后抛出locked错误——既不会快速失败,也不会无限挂起;另一个测试进一步验证等待期间通过STEP_SLEEP让出事件循环,不会阻塞 Node.js 主线程。

执行 DDL:exec()一次运行多条语句

连接建立后,示例用exec一次性执行建表和建索引两条语句:

await db.exec(` CREATE TABLE IF NOT EXISTS guestbook (comment TEXT, created_at DEFAULT (unixepoch())); CREATE INDEX IF NOT EXISTS guestbook_idx ON guestbook (created_at); `);

exec适合"只需要执行到完成、不关心返回值"的语句(DDL、批量 INSERT 等),支持以分号分隔的多条 SQL。created_at使用 SQLite 内置的unixepoch()函数生成秒级时间戳作为默认值,再为该列建索引以加速后续按时间倒序的查询——这为后面ORDER BY created_at DESC的读取做好了铺垫。

从 common/promise.ts 的源码结构看,Database类内部维护了一把execLock(异步锁),所有语句执行都在其上串行化,保证同一连接上并发调用不会出现交叉污染。

预处理语句与参数绑定:prepare()+run()/all()/get()

接下来是示例的核心演示——预处理语句与占位符绑定:

// use prepared statements and bind args to placeholders later const insert = db.prepare(`INSERT INTO guestbook(comment) VALUES (?)`); // use run(...) method if query only need to be executed till completion await insert.run([`hello, turso at ${Math.floor(Date.now() / 1000)}`]); const select = db.prepare(`SELECT * FROM guestbook ORDER BY created_at DESC LIMIT ?`); // use all(...) or get(...) methods to get all or one row from the query console.info(await select.all([5]));

这里展示的是完整的数据访问模式:

  1. db.prepare(sql):预编译一条 SQL 语句,返回Statement实例。SQL 中的?是位置占位符,运行时再绑定具体值,避免字符串拼接带来的注入风险,同时语句只解析编译一次、可重复执行。
  2. stmt.run(...args):执行"只求完成不求结果"的语句(INSERT/UPDATE/DELETE 等),参数以数组或展开形式传入。
  3. stmt.all(...args):执行查询并返回所有匹配行;stmt.get(...args)则只取第一行。示例中select.all([5])即查询按时间倒序的最新 5 条留言,结果以对象数组形式打印到控制台。

此外,native 包 README 还展示了run()的返回值用法:const result = await insertPost.run('Hello World', '...')之后可以通过result.lastInsertRowid拿到刚插入行的 rowid。与之对应的底层能力在 index.d.ts 中均有暴露:Database.lastInsertRowid()changes()totalChanges(),以及Statement上的parameterCount()parameterName(index)bindAt()columns()等元信息与绑定 API。

Statement还支持raw()/pluck()两种展示模式切换,以及safeIntegers()控制大整数是否以安全整数返回,setQueryTimeout()为单条语句设置超时,用完可调用finalize()显式释放。

事务:transactionAsync()保证原子性

虽然index.mjs示例本身没有演示事务,但这是嵌入式数据库使用中不可或缺的一环,native 包 README 给出了完整示例:

import { connect } from '@tursodatabase/database'; const db = await connect('transactions.db'); // Using transactions for atomic operations const transaction = db.transactionAsync(async (txn, users) => { const insert = await txn.prepare('INSERT INTO users (name, email) VALUES (?, ?)'); for (const user of users) { await insert.run(user.name, user.email); } }); // Execute transaction await transaction([ { name: 'Alice', email: 'alice@example.com' }, { name: 'Bob', email: 'bob@example.com' } ]);

transactionAsync(fn)接受一个异步回调,回调首个参数是Transaction句柄(内部的prepare/exec/run都必须在txn上调用),后续参数由调用方传入。其实现位于 common/promise.ts:执行前先获取execLock,回调体前后分别执行BEGINCOMMIT/ROLLBACK,从而把整段逻辑包进一个原子事务;锁在事务提交/回滚后才释放,避免其他语句穿插进事务区间。旧的transaction()同步包装已被标记为 deprecated,新代码应统一使用transactionAsync

进阶能力:内存数据库、加密与兼容层

结合 native 包 README 与源码,还可以看到示例之外的几类进阶用法:

内存数据库

const db = await connect(':memory:'); await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)'); const users = await db.prepare('SELECT * FROM users').all();

适合测试与临时计算场景,数据随连接关闭即消失。Database类型上对应的memoryreadonlyopenpath属性在 index.d.ts 中均有声明,可用于运行时自检。

本地加密

promise.tsDatabase构造器会把字符串形式的 cipher 名称映射为 native 枚举值后传入底层:支持aes128gcmaes256gcmaegis256aegis256x2aegis128laegis128x2aegis128x4七种算法,配合 hex 编码密钥使用:

const db = await connect('encrypted.db', { encryption: { cipher: 'aes256gcm', hexkey: '...' }, });

若当前构建的 native 模块未启用加密,会在映射时直接抛出 "Encryption is not supported in this build" 错误。仓库内另有 examples/javascript/encryption 专门演示这一能力。

兼容层与生态集成

包入口还导出了./compat兼容模块(对应compat.ts),提供贴近 better-sqlite3 风格的同步式 API,便于存量代码迁移。原生异步 API 则可通过 promise.test.ts 中drizzle-orm的集成测试看到生态适配情况:drizzle(conn)包装连接后即可执行 ORM 风格的db.run/db.all,测试中 1234 条并发 INSERT 全部成功并正确计数。

底层原理:NAPI-RS 原生绑定与异步 I/O 循环

@tursodatabase/database并不是一个纯 JS 实现,而是通过 NAPI-RS 将仓库根目录 Cargo.toml 下用 Rust 编写的 Turso 嵌入式引擎编译为原生模块(napi build产物),JS 层只是薄封装。从 bindings/javascript/packages/native/package.json 的napi.targets可以看出官方预编译的四个平台目标:x86_64-unknown-linux-gnux86_64-pc-windows-msvcaarch64-apple-darwinaarch64-unknown-linux-gnu,即覆盖主流 Linux/macOS/Windows 的 x86_64 与 arm64 架构。

异步模型上,底层Database暴露了ioLoopSync()/ioLoopAsync()Statement.stepSync()每次步进返回[step, sleepMs]step取值 1(有行可读)、2(执行完毕)、3(需要 I/O)、4(请求休眠),sleepMs仅在STEP_SLEEP时非零。JS 层据此实现"需要 I/O 就让出事件循环、需要等待锁就定时唤醒"的非阻塞执行策略——这正是上文 busy timeout 测试中"等待期间不阻塞事件循环"的机制来源。此外classifySql()可将任意 SQL 归类为read / write / begin / commit / rollback,供执行策略与锁管理参考。

需要更完整的 API 说明时,可查阅仓库内的 JavaScript API Reference;SQLite 语法与文件格式兼容状态见 COMPAT.md。

小结

官方 database-node 示例以不足 30 行代码覆盖了嵌入式数据库使用的全部核心链路:connect建连并配置 busy timeout →exec执行多语句 DDL →prepare预编译 + 占位符绑定 →run写入 →all读取。以此为基础,再结合transactionAsync事务、内存库、加密与兼容层等进阶能力,即可在 Node.js 应用中快速获得一个进程内运行、SQLite 兼容、具备并发写保护与事务能力的本地数据库引擎。所有 API 的精确行为都可在 bindings/javascript/packages/native 与 bindings/javascript/packages/common 的源码及测试中找到对应实现,便于你按需深入。

【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询