Chapter 03

libSQL JS SDK 实战

@libsql/client 连上 Turso,跑通增删改查、批处理、事务;分清 Node、Edge、浏览器三种运行时的驱动差异。

3.1 安装

npm install @libsql/client
# 或
pnpm add @libsql/client
bun add @libsql/client

包内包含两个入口,自动按运行时选择:

3.2 第一次连接

import { createClient } from '@libsql/client';

const db = createClient({
  url: process.env.TURSO_DATABASE_URL!,      // libsql://...turso.io
  authToken: process.env.TURSO_AUTH_TOKEN!,
});

const res = await db.execute('SELECT 1 as x');
console.log(res.rows);   // [{ x: 1 }]

Edge 运行时

import { createClient } from '@libsql/client/web';

const db = createClient({
  url: env.TURSO_DATABASE_URL,
  authToken: env.TURSO_AUTH_TOKEN,
});

/web 版仅用 fetch,能在 Cloudflare Workers(无 TCP)、Vercel Edge、Deno Deploy、浏览器中运行。功能集稍窄:不支持嵌入式副本(要文件系统),其他 SQL 操作完全一致。

3.3 execute:运行单条 SQL

// 简单查询
const { rows } = await db.execute('SELECT * FROM users LIMIT 10');

// 参数绑定(位置)
await db.execute({
  sql: 'SELECT * FROM users WHERE email = ?',
  args: ['alice@example.com'],
});

// 参数绑定(命名)
await db.execute({
  sql: 'INSERT INTO users (name, age) VALUES (:name, :age)',
  args: { name: 'Alice', age: 30 },
});
永远参数绑定

不要拼字符串。libSQL 的 ?:name 会做二进制级别的绑定,防 SQL 注入、减少 parse 开销。

3.4 返回结构

interface ResultSet {
  columns: string[];         // ['id', 'name']
  rows: Row[];               // [{ id:1, name:'A' }, ...]
  rowsAffected: number;      // UPDATE/DELETE 的行数
  lastInsertRowid: bigint;   // INSERT 的 rowid(注意是 bigint)
}

lastInsertRowid 在 JS 里是 bigint——JSON.stringify 会抛错,要写成字符串。

3.5 类型映射

SQLite 列类型JS 返回类型
INTEGER(< 2^53)number
INTEGER(更大)bigint(需 intMode: 'bigint')
REALnumber
TEXTstring
BLOBUint8Array
NULLnull

配置整数模式:

const db = createClient({
  url, authToken,
  intMode: 'bigint',   // 全部 int 作为 bigint
  // 或 'string'(适合前端 JSON)
});

3.6 批量:batch

一次 RTT 发多条 SQL,全部成功才提交(自动包在事务里):

await db.batch([
  'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)',
  { sql: 'INSERT INTO users (name) VALUES (?)', args: ['Alice'] },
  { sql: 'INSERT INTO users (name) VALUES (?)', args: ['Bob'] },
], 'write');    // 'read' | 'write' | 'deferred'

第二个参数决定事务模式:

3.7 交互式事务:transaction

当你需要"先读、再决定是否写"时用交互式事务:

const tx = await db.transaction('write');
try {
  const { rows } = await tx.execute('SELECT balance FROM accounts WHERE id=?', [1]);
  if (rows[0].balance >= 100) {
    await tx.execute('UPDATE accounts SET balance=balance-100 WHERE id=?', [1]);
    await tx.execute('UPDATE accounts SET balance=balance+100 WHERE id=?', [2]);
  }
  await tx.commit();
} catch (e) {
  await tx.rollback();
  throw e;
}
交互式事务 = 锁住的连接

打开 transaction 后,primary 上的该数据库实际上被你的客户端锁住(读写锁)。用完一定 commit/rollback——忘了的话锁会超时释放(默认 5 秒),期间其他写会被拒。

3.8 连接池?不需要

libSQL 是 HTTP/WebSocket 协议——没有 TCP 连接池的概念。一个 createClient 出来的 client 是可以全局共享的,内部自动管理 HTTP 连接复用。

Edge 环境里:每个请求里 createClient 一次也 OK(开销仅是对象创建),但如果走 module scope 会更好:

// lib/db.ts
import { createClient } from '@libsql/client/web';
export const db = createClient({ url, authToken });

// 各路由直接 import { db } from '@/lib/db'

3.9 错误处理

import { LibsqlError } from '@libsql/client';

try {
  await db.execute('INSERT INTO users(email) VALUES(?)', ['a@a.com']);
} catch (e) {
  if (e instanceof LibsqlError) {
    console.log(e.code);    // 'SQLITE_CONSTRAINT_UNIQUE'
    console.log(e.rawCode); // SQLite errno
  }
}

常见 code:SQLITE_CONSTRAINT_UNIQUESQLITE_BUSYSQLITE_READONLYHRANA_PROTO_*(协议错误)。

3.10 跨运行时:Node/Bun/Deno/Workers

运行时导入路径嵌入式副本备注
Node.js@libsql/clientbest-sqlite3 原生
Bun@libsql/clientbun:ffi 加速
Denonpm:@libsql/client需 --unstable-ffi
Cloudflare Workers@libsql/client/webfetch 驱动
Vercel Edge@libsql/client/webfetch 驱动
浏览器@libsql/client/webCORS 需代理

3.11 最小示例:Hono + Turso

import { Hono } from 'hono';
import { createClient } from '@libsql/client/web';

type Env = { Bindings: { TURSO_URL: string; TURSO_TOKEN: string } };
const app = new Hono<Env>();

app.get('/users', async (c) => {
  const db = createClient({
    url: c.env.TURSO_URL,
    authToken: c.env.TURSO_TOKEN,
  });
  const { rows } = await db.execute('SELECT id, name FROM users');
  return c.json(rows);
});

export default app;

小结

libSQL JS SDK 的 4 个核心 API:execute(单条)、batch(多条/事务)、transaction(交互式)、sync(嵌入式副本,下章讲)。区分 Node 版和 /web 版决定了你能不能跑在 Edge。下一章我们挖 Turso 最酷的特性——嵌入式副本。