@bunny.net/database-client

中文文档
0.0.3发布于 2026/09/02MITTypeScriptES Module零依赖近 30 天下载 1985

Bunny Database 无依赖的 SQL 客户端 。 任何地方抓取的跑道都是: Bunny Edge Scripting, Bun, Node, 和 Deno.

中文使用文档
文档目录18 节
  1. 安装
  2. 快开始
  3. API 密码
  4. connect(config?)
  5. db.prepare(sql)
  6. db.sql`...`
  7. statement.bind(...values)
  8. 执行
  9. db.batch(statements, options?)
  10. db.exec(sql)
  11. 错误
  12. 类型
  13. 边缘脚本
  14. 警卫
  15. 为什么没有浏览器
  16. 怎么办呢
  17. 处理令牌
  18. 发展

@bunny.net/数据库客户端

一个小型 SQL 客户端 兔子数据库。它没有依赖性和用途 fetch 所以同样的代码在Bunny Edge脚本(Deno),Bun,和Node上运行.

[尖叫声] 仅限服务器侧. 永远不要将它运送给浏览器或其他任何不信任的客户端.

一个认证符允许访问整个数据库,这个客户端发送原始的SQL. 输入客户端代码 每位访客都可以读写每张桌子 不管你的UI提供什么. 为什么没有浏览器 有细节.

安装

bun add @bunny.net/database-client

快开始

connect() 读取 BUNNY_DATABASE_URL 财务报告和审定财务报表 BUNNY_DATABASE_AUTH_TOKEN a. 与《公约》有关的其他事项 bunny db quickstart --lang typescript 您的指纹( 或 bunny db create --token --save-env 直接写入),且边缘脚本已经设置的 :

import { connect } from "@bunny.net/database-client";

const db = connect();

const user = await db.prepare("SELECT * FROM users WHERE id = ?").bind(1).first();

需要时明确通过:

const db = connect({
  url: "libsql://your-db.lite.bunnydb.net",
  authToken: "your-token",
});

Edge 脚本、 Bun、 节点、 Hono、 Next.js、 Astro 和 SvelteKit 的可运行示例 packages/database-client/examples。 。 。 .

API 密码

connect(config?)

回返a Database。每个选项都是可选的.

选项类型默认说明
urlstringBUNNY_DATABASE_URLlibsql://, (中文(简体) ), https://,或 http:// 连接 URL.
authTokenstringBUNNY_DATABASE_AUTH_TOKEN发送为 Authorization: Bearer <token>。 。 。 .
fetchtypeof fetch全球 fetch代替测试、追踪或定制代理.
headersRecord<string, string>无每一个请求都要加头.
signalAbortSignal无适用于每一个请求.
timeoutnumber无单项请求中止前的毫秒数 。 正整数可达2147483647.

客户端重写 a libsql:// URL为 https://。它拒绝在 URL 中的证书,两者 user:pass@ 还有一个 authToken 查询参数,所以通过 authToken 换句话说 默默地放出一个符号 会让你调试一个无法解释的401.

每个请求都携带 User-Agent: bunny-database-client中页眉名称 headers 被匹配的大小写不敏感, 所以通过自己的 User-Agent 替换默认,而不是同时发送.

无 timeout 请求在运行时间允许的情况下等待,在边缘函数上意味着被挂起的取来可以将整个引用点烧掉. timeout 财务报告和审定财务报表 signal 作曲:以哪个起火先中止请求.

db.prepare(sql)

回返a Statement. 语句是不可改变的,所以你可以保持一个周围,并按你喜欢的频率绑住它.

const byId = db.prepare("SELECT * FROM users WHERE id = ?");

const alice = await byId.bind(1).first();
const bob = await byId.bind(2).first();

传出一行的字型,使其贯穿该语句的每次执行. 见 类型。 。 。 .

db.sql`...`

绑定每个内插值的模板文字,所以写出查询的最短方式也是参数化的:

const note = await db.sql`SELECT * FROM notes WHERE id = ${id}`.first();

每个 ${...} 变成一个 ? 占位符及其值是绑定的,从未被分解入 SQL 字符串。 它返回一个 Statement,所以所有的东西 执行 适用情况不变.

数值遵循与 bind(),有一个区别:插入对象抛出而不是被读取为被命名的参数,因为在模板中,它更有可能是一个错误.

用同样的方式通过一行 prepare(),作为 db.sql<User>`...` 。 。 。 .

只有值可以参数化,它是一个SQLite限制而不是客户端限制. 以 prepare() 当表格或列名必须更改时.

statement.bind(...values)

绑定参数并返回新语句。 接受 null, (中文(简体) ), boolean, (中文(简体) ), number, (中文(简体) ), bigint, (中文(简体) ), string,以及 Uint8Array。 。 。 .

传出值为 ? 占位符 :

await db.prepare("SELECT * FROM users WHERE id = ? AND active = ?").bind(1, true).all();

为 SQLite 的命名表单通过一个单一对象, :name, (中文(简体) ), @name,以及 $name数字 :

await db
  .prepare("SELECT * FROM users WHERE id = :id AND active = :active")
  .bind({ id: 1, active: true })
  .all();

名字可能带了相片,或者把它留下,所以 { id: 1 } 财务报告和审定财务报表 { ":id": 1 } 两者都绑定 :id。一个语句使用一种样式:将位置值和对象混合在同一语句中 bind() 而不是为你挑一个赢家.

undefined 扔出而不是绑定 NULL,所以错误的属性(bind(user.nmae))表面而不是悄悄地写出NULL. 过 null 当你指NULL.

任何其它的投出,而不是被悄悄地转换,因为SQLite无处可放. Date 得到自己的信息建议 .toISOString() 或者说 .getTime()既然你猜到哪一个会改变 最终在栏目.

整数 numbers是作为INTEGER发送的,而它们完全合适,最高可达2^53. 过去的每双都是整数,所以客户端发送时是真实的,真实的存储值是不变的. 传出 a bigint 当你需要一个那么大的整数。 Bigints必须符合SQLite的64位签名范围.

执行

运行声明的四种方式:

const rows = await db.prepare("SELECT id, name FROM users").all();
// [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]

const row = await db.prepare("SELECT * FROM users WHERE id = ?").bind(1).first();
// { id: 1, name: "Alice" }  or  null

const name = await db.prepare("SELECT name FROM users WHERE id = ?").bind(1).first("name");
// "Alice"  or  null

const rows = await db.prepare("SELECT id, name FROM users").raw();
// [[1, "Alice"], [2, "Bob"]]

run() 返回行加写入元数据 :

const result = await db
  .prepare("INSERT INTO users (name) VALUES (?) RETURNING id")
  .bind("Carol")
  .run();

// {
//   rows: [{ id: 3 }],
//   columns: ["id"],
//   rowsAffected: 1,
//   lastInsertRowid: 3,
// }

runRaw() 返回与位置数组相同的有行的元数据。 当结果可能包含同名的两列时, 联系它, 因为对象行只保留上一列 :

const result = await db.prepare("SELECT a.id, b.id FROM a JOIN b").runRaw();
// { rows: [[1, 99]], columns: ["id", "id"], rowsAffected: 0, lastInsertRowid: null }

在其中之一被调用之前,声明什么都不做,所以 prepare() 财务报告和审定财务报表 bind() 安全地通过.

db.batch(statements, options?)

在一次交易和一次往返中运行每个语句。 他们都承诺或没有.

const [inserted, count] = await db.batch([
  db.prepare("INSERT INTO users (name) VALUES (?)").bind("Dan"),
  db.prepare("SELECT COUNT(*) AS c FROM users"),
]);

你得到一个 Result 按顺序排列. batchRaw() 相同但有位置行。 如果有声明失败, 交易会回滚 batch() 丢弃该语句错误, 与 error.batchIndex 设置到失败的语句位置.

批次是交易,所以你自己的声明开始 BEGIN, (中文(简体) ), COMMIT, (中文(简体) ), END,或 ROLLBACK 在发送任何信息之前被拒绝。 保存点是好的.

{ mode: "immediate" } 打开交易 BEGIN IMMEDIATE写字锁在前面。 SQLite的默认值, deferred,在首个写作时取下它,这样一批读取然后写作就会失败 SQLITE_BUSY 如果另一个作家进来了 使用 immediate 你认识的批次会写. exclusive 也予以接受.

batch() 从语句中推断出每个结果的行类型,所以 prepare<User>(...) 语句恢复为 Result<User> 乃至于无型相相相相. 见 类型。 。 。 .

{ foreignKeys: false } 将交易以 PRAGMA foreign_keys=off 财务报告和审定财务报表 =on。Schema 更改需要它: SQLite 的表格重建程序和若干 ALTER TABLE 形式要求真正取消执法,而不仅仅是延后实施. bunny db migrations apply 运行这边.

await db.batch(
  [
    db.prepare("CREATE TABLE users_new (id INTEGER PRIMARY KEY, email TEXT NOT NULL)"),
    db.prepare("INSERT INTO users_new SELECT id, email FROM users"),
    db.prepare("DROP TABLE users"),
    db.prepare("ALTER TABLE users_new RENAME TO users"),
  ],
  { foreignKeys: false, mode: "immediate" },
);

保持这个顺序:以临时名称来构建替换,将原名放下后再重命名. 重新命名原版,让原版脱颖而出。 。 REFERENCES users 在其它表格中,要跟随重命名,它们最后指向一个你即将掉落的表格.

db.exec(sql)

运行多语句脚本。 它不需要参数,也不返回行,所以它主要用于设置一个计划.

await db.exec(`
  CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
  CREATE INDEX IF NOT EXISTS users_name ON users (name);
`);

任何你需要运行不止一次, 到达移民(bunny db migrations相反.

错误

一切抛出 DatabaseError数字 :

import { DatabaseError } from "@bunny.net/database-client";

try {
  await db.prepare("INSERT INTO users (email) VALUES (?)").bind("[email protected]").run();
} catch (error) {
  if (error instanceof DatabaseError && error.code === "SQLITE_CONSTRAINT") {
    // handle the duplicate
  }
}
财产说明
message服务器的信息,或对本地验证失败的描述.
codeSQLite 代码 (SQLITE_CONSTRAINT),或客户端代码(UNAUTHORIZED, (中文(简体) ), URL_MISSING) (中文(简体) ).
statusHTTP状态,当故障来自运输而不是从SQL.
batchIndex失败的语句的位置, 当您之一的语句在 batch() 失败 .

在SQL运行之前发生的失败也被包住,所以一个 catch (error) { if (error instanceof DatabaseError) ... } 覆盖整个地表 而不是让一个 TypeError 通过:

code原因
NETWORKDNS,TLS,连接拒绝,或者回应不是JSON.
TIMEOUT这个 timeout 截止日期已过.
ABORTED这个 signal 你通过了 流产.
PROTOCOL服务器用一个不是管道反应的躯体回答200.

对于这三人, error.cause 保存运行时间的原错误 .

类型

整数者回来 number 而他们完全合身 bigint 外加 Number.MAX_SAFE_INTEGER。客户端从不检查一个值使其适合.

SQL 点Java 脚本
无效null
进口number,或 bigint 历经2^53
真实number
文本string
块Uint8Array

SQLite 没有布尔类型 . bind(true) 存储 1,并读回 1。 。 。 .

行为 Record<string, SqlValue> 默认。 传出自己的形状来跳过铸币局:

interface User {
  id: number;
  name: string;
}

const users = await db.prepare("SELECT id, name FROM users").all<User>();

或者一次输入语句,让其每次执行都继承形状:

const byId = db.prepare<User>("SELECT id, name FROM users WHERE id = ?");

const alice = await byId.bind(1).first(); // User | null
const both = await byId.bind(1).all(); // User[]

执行器上的一类参数仍然胜过声明,所以 all<Row>() 编号 Statement<User> 让你排回未打过的字.

这种类型是一种断言。 在运行时没有任何东西可以验证行与它相对,所以它只能像您的SQL那样准确.

边缘脚本

Edge脚本运行 Deno,因此可以从npm直接导入. 一个独立的脚本通过边框脚本SDK服务请求:

import * as BunnySDK from "npm:@bunny.net/[email protected]";
import { connect } from "npm:@bunny.net/database-client";

const db = connect();

BunnySDK.net.http.serve(async (request: Request): Promise<Response> => {
  const users = await db.prepare("SELECT id, name FROM users LIMIT 10").all();
  return Response.json(users);
});

在模块范围构建客户端是好的. connect() 打开无套接字和 I/O , 所以没有需要加热或拆卸的请求 .

Edge Script也是持有数据库符号的正确地点. 代码及其环境一直停留在兔子的边缘上,浏览器只看到了你选择返回的响应.

警卫

为什么没有浏览器

客户端只使用 fetch,所以它会很高兴运行 在浏览器。 它还是不应该去那里.

一个数据库认证令牌授权连接,这意味着任何持有它的东西都可以运行任何令牌允许的SQL对接任何表格. 在客户端代码中,符号出现在网络标签、JS捆绑、 localStorage,以及任何注入的脚本。 一旦它泄露,一个访客可以尽你所能, DROP TABLE 包含.

值得我们知道: bunny db tokens create 因此,你最有可能躺在周围的标志也是最糟糕的.

仅读的符号缩小了损坏而不修复:

bunny db tokens create --read-only --expiry 30d

这仍然把每张表格的每行都交出来,因为符号授权连接而不是查询,而SQLite没有行级的安全性可以回落.

怎么办呢

在您的服务器上保留此令牌, 只披露您实际想要允许的查询 。 通常指坐在数据库前的边缘脚本:

// Edge Script: the token stays here, the browser gets only this shape.
import * as BunnySDK from "npm:@bunny.net/[email protected]";
import { connect } from "npm:@bunny.net/database-client";

const db = connect();

BunnySDK.net.http.serve(async (request: Request): Promise<Response> => {
  const url = new URL(request.url);
  const author = url.searchParams.get("author");
  if (!author) return new Response("author required", { status: 400 });

  // Parameterized, and scoped to the columns and rows the caller may see.
  const posts = await db
    .prepare("SELECT id, title FROM posts WHERE author = ? AND published = 1 LIMIT 50")
    .bind(author)
    .all();

  return Response.json(posts);
});

浏览器调用您的端点,您的端点决定了SQL的运行.

处理令牌

  • 在环境变量中而不是在源中保留符号. connect() 读取 BUNNY_DATABASE_URL 财务报告和审定财务报表 BUNNY_DATABASE_AUTH_TOKEN,所以一个符号 从来没有出现在代码.
  • 连接 URL 中的证书被拒绝,因为 URL 最终会出现在日志, 裁判员和错误报告中. 过 authToken 换句话说.
  • 首选短寿活符(bunny db tokens create --expiry 12h)和最狭小的授权有效. 如果有人漏掉的话, bunny db tokens invalidate 取消数据库的每个符号.
  • DatabaseError 携带服务器的信息和SQLite代码,所以直接传递到客户端可以泄露出计划细节. 记录下来,然后还一些通用的.

发展

bun test

scripts/smoke.ts 运行客户端与两个运行时间的真实数据库。 它创造和放弃自己的桌子, 并留下任何东西:

bun run scripts/smoke.ts
deno run --env-file=.env --allow-net --allow-env scripts/smoke.ts

版本与发布记录

@bunny.net/database-client 共发布 3 个版本,首次发布于 2026/08/25;过去 12 个月发布了 3 个版本。

版本发布日期距今
0.0.3latest2026/09/022 个月前
0.0.22026/09/012 个月前
0.0.12026/08/252 个月前

关于 @bunny.net/database-client 的常见问题

@bunny.net/database-client 是什么?

Bunny Database 无依赖的 SQL 客户端 。 任何地方抓取的跑道都是: Bunny Edge Scripting, Bun, Node, 和 Deno.

如何安装 @bunny.net/database-client?

在项目目录运行 npm install @bunny.net/database-client,或使用 pnpm add / yarn add / bun add。

@bunny.net/database-client 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bunny.net/database-client 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bunny.net/database-client 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.17.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bunny.net/database-client 可以商用吗?

@bunny.net/database-client 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bunny.net/database-client 的下载量有多少?

根据 npm 官方下载统计,@bunny.net/database-client 在 2026/10/09 – 2026/11/07 这 30 天内被下载 1,985 次,日均约 66 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bunny.net/database-client 还在维护吗?

最新版本 0.0.3 发布于 2026/09/02(2 个月前);至今共发布 3 个版本,其中过去 12 个月发布了 3 个;首次发布于 2026/08/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。