본문 바로가기
WIKI 기술 지식 베이스

레퍼런스

원문 보기 위키 갱신

Turso의 TypeScript 레퍼런스예요. 네 가지 패키지가 각각 어떤 상황에 맞는지, 연결·쿼리·트랜잭션·동기화·암호화 API를 하나씩 살펴볼게요.

출처: 문서

본문

Turso는 네 가지 TypeScript 패키지를 제공해요:

@tursodatabase/database @tursodatabase/sync @tursodatabase/serverless @libsql/client
Use case 로컬/임베디드 데이터베이스 로컬 데이터베이스 + 클라우드 sync 원격 Turso 데이터베이스 (서버, 컨테이너, serverless, edge) 원격 libSQL 데이터베이스, ORM 지원 (Drizzle, Prisma)
Engine Turso (rewrite) Turso (rewrite) Turso libSQL (SQLite fork)
Dependencies Native (Node.js, WASM) Native (Node.js) fetch만 — 네이티브 의존성 없음 Node.js 또는 /web 서브패스 필요
Concurrent writes Yes (MVCC) Yes (MVCC) Yes (MVCC) Not supported
Sync — push/pull (local-first) — Embedded Replicas (쓰기는 클라우드 primary로)
ORM support Drizzle (beta) — — Drizzle, Prisma 등

새 프로젝트를 시작하나요? 로컬/임베디드 용도에는 @tursodatabase/database, 로컬 + 클라우드 sync에는 @tursodatabase/sync, 네트워크를 넘어 원격 Turso 데이터베이스에 접근할 때는 @tursodatabase/serverless를 쓰세요(Node.js 서버, Docker 컨테이너, serverless 함수, 엣지 런타임). 원격 libSQL 데이터베이스에 연결하거나 ORM을 쓰나요? @libsql/client를 사용하세요 — 프로덕션에서 검증됐고 Drizzle, Prisma 등이 지원해요. Drizzle은 로컬/임베디드 용도의 @tursodatabase/database에 대한 베타 지원도 있고요.

호환성이 확인된 런타임 환경은 다음과 같아요:

  • Node.js 12 이상
  • Deno
  • CloudFlare Workers
  • Netlify & Vercel Edge Functions

@tursodatabase/database

로컬·임베디드 용도예요. 동시 쓰기(MVCC)와 비동기 I/O를 지원하는 Turso Database 엔진 기반이에요.

Installing

npm install @tursodatabase/database

Initializing

import { connect } from "@tursodatabase/database";

const db = await connect("app.db");

인메모리 데이터베이스도 지원해요:

const db = await connect(":memory:");

Querying

const stmt = db.prepare("SELECT * FROM users");
const users = await stmt.all();

const insert = db.prepare("INSERT INTO users (username) VALUES (?)");
await insert.run("alice");

Encryption

encryption 옵션으로 로컬 데이터베이스를 저장 시점(at rest)에 암호화할 수 있어요:

import { connect } from "@tursodatabase/database";

const db = await connect("encrypted.db", {
  encryption: {
    cipher: "aegis256",
    hexkey: "b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327",
  },
});

지원 암호: aegis256, aegis256x2, aegis128l, aegis128x2, aegis128x4, aes256gcm, aes128gcm.

암호화된 데이터베이스는 표준 SQLite 데이터베이스로는 읽을 수 없어요 — 반드시 Turso Database 엔진으로 열어야 해요.

참고로, Turso Cloud 데이터베이스도 bring-your-own-key로 암호화할 수 있어요 — 자세히 보기.

@tursodatabase/sync

클라우드 sync가 있는 로컬 데이터베이스용이에요. 모든 읽기와 쓰기는 로컬에서 일어나고, push()로 변경을 클라우드에 보내고 pull()로 원격 변경을 가져와요.

Installing

npm install @tursodatabase/sync

Initializing

import { connect } from "@tursodatabase/sync";

const db = await connect({
  path: "./app.db",
  url: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

첫 실행 때 로컬 데이터베이스가 원격에서 자동으로 bootstrap돼요. 전체 내용은 Turso Sync에서 확인하세요.

Push and Pull

// Push local writes to Turso Cloud
await db.push();

// Pull remote changes to local database
const changed = await db.pull();

Checkpoint

sync 상태를 유지하면서 로컬 WAL을 압축해 디스크 사용량을 제한해요:

await db.checkpoint();

Stats

const s = await db.stats();
console.info({
  cdcOperations: s.cdcOperations,
  mainWalSize: s.mainWalSize,
  networkReceivedBytes: s.networkReceivedBytes,
  networkSentBytes: s.networkSentBytes,
  lastPullUnixTime: s.lastPullUnixTime,
  lastPushUnixTime: s.lastPushUnixTime,
  revision: s.revision,
});

@tursodatabase/serverless

원격 Turso Cloud 데이터베이스에 연결하는 모든 애플리케이션에 추천하는 패키지예요 — Node.js 서버, Docker 컨테이너, serverless 함수(AWS Lambda, Vercel Functions), 엣지 런타임(Cloudflare Workers, Deno Deploy). fetch만 사용하기 때문에 네이티브 의존성이 전혀 없고, fetch가 있는 곳이라면 어디서든 동작해요.

Installing

npm install @tursodatabase/serverless

Initializing

import { connect } from "@tursodatabase/serverless";

const conn = connect({
  url: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

@libsql/client API와의 호환성이 필요하면 compat 모듈을 쓰세요:

import { createClient } from "@tursodatabase/serverless/compat";

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

Querying

const stmt = await conn.prepare("SELECT * FROM users");
const rows = await stmt.all();

const stmt2 = await conn.prepare("SELECT * FROM users WHERE id = ?");
const row = await stmt2.get([1]);

@libsql/client

@libsql/client 패키지는 수년간 Turso Cloud를 구동해 온 SQLite 오픈소스 포크인 libSQL 기반이에요. 프로덕션에서 검증이 끝났고, libSQL 데이터베이스 원격 접근, Drizzle을 넘어선 ORM 연동(예: Prisma), 기존 @libsql/client 기반 코드베이스 작업에 맞는 선택이에요.

참고로, @libsql/client Embedded Replicas에서는 읽기가 로컬에서 일어나고 쓰기는 클라우드 primary로 전송된 뒤 복제본에 반영돼요. Embedded Replicas는 완전히 지원돼요. sync가 필요한 새 프로젝트에는 Turso Sync의 @tursodatabase/sync를 추천해요.

Installing

@libsql/client 패키지를 설치하면 돼요: npm install @libsql/client

Initializing

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

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

참고로, libsql을 로컬에서 쓰거나 sqlite 파일을 다룬다면 authToken은 넘기지 않아도 돼요.

In-Memory Databases

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

const client = createClient({
  url: ":memory:",
});

Local Development

SQLite 파일 경로를 createClient에 넘겨서 로컬에서 작업할 수 있어요:

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

const client = createClient({
  url: "file:path/to/db-file.db",
  authToken: "...",
});

주의할 점은, @libsql/client/web은 로컬 파일 URL을 지원하지 않아요.

Embedded Replicas

오프라인 쓰기, 양방향 sync, 다중 writer 수렴이 필요한 워크로드에는 @tursodatabase/sync를 추천해요 — 읽기와 쓰기가 모두 로컬에서 일어나고, push() / pull()로 명시적으로 동기화하거든요.

Turso Database URL을 syncUrl에 넘겨서 embedded replicas를 쓸 수 있어요:

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

const client = createClient({
  url: "file:path/to/db-file.db",
  syncUrl: "libsql://[databaseName]-[organizationSlug].turso.io",
  authToken: "...",
});

Manual Sync

await client.sync();

Periodic Sync

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

const client = createClient({
  url: "file:path/to/db-file.db",
  syncUrl: "libsql://[databaseName]-[organizationSlug].turso.io",
  syncInterval: 60,
  authToken: "...",
});

Encryption

새 프로젝트에서는 로컬 암호화에 @tursodatabase/database를 추천해요 — Turso Database 엔진 기반이라 성능이 좋고 동시 쓰기를 지원하거든요.

암호화된 데이터베이스는 원시 데이터로 보이고 표준 SQLite 데이터베이스로는 읽을 수 없어요. 모든 작업에 libSQL 클라이언트를 써야 해요 — 자세히 보기.

Concurrency

기본적으로 클라이언트는 최대 20개의 동시 요청을 처리해요:

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

const client = createClient({
  concurrency: 10,
});

Response

아래 나열된 각 메서드는 Promise<ResultSet>을 반환해요:

Property Type Description
rows Array<Row> 행 값을 담은 Row 객체 배열, 쓰기 작업에서는 비어 있음
columns Array<string> 각 Row에 나타나는 순서대로 컬럼 이름을 담은 문자열 배열, 쓰기 작업에서는 비어 있음
rowsAffected number 쓰기 문장이 영향을 준 행 수, 그 외에는 0
lastInsertRowid bigint | undefined 새로 삽입된 행의 ID, 문장에 없으면 undefined

Simple query

문자열이나 객체를 execute()에 넘겨 SQL 문을 실행할 수 있어요:

String

const result = await client.execute("SELECT * FROM users");

Object

const result = await client.execute({
  sql: "SELECT * FROM users WHERE id = ?",
  args: [1],
});

Placeholders

libSQL은 SQL 문 안에서 positional 및 named 플레이스홀더를 지원해요:

Positional

const result = await client.execute({
  sql: "SELECT * FROM users WHERE id = ?",
  args: [1],
});

const result = await client.batch(
  [
    {
      sql: "INSERT INTO users VALUES (?)",
      args: ["Iku"],
    },
  ],
  "write",
);

Named

const result = await client.execute({
  sql: "INSERT INTO users VALUES (:name)",
  args: { name: "Iku" },
});

const result = await client.batch(
  [
    {
      sql: "INSERT INTO users VALUES (:name)",
      args: { name: "Iku" },
    },
  ],
  "write",
);

참고로, libSQL은 SQLite와 같은 named 플레이스홀더 문자를 지원해요 — :, @, $.

Transaction Modes

Mode SQLite command Description
write BEGIN IMMEDIATE 트랜잭션이 데이터를 읽고 쓰는 문장을 실행할 수 있어요. 복제본에서 실행된 write 트랜잭션은 primary로 전달되며, 병렬로 동작할 수 없어요.
read BEGIN TRANSACTION READONLY 트랜잭션이 데이터를 읽는 문장(select)만 실행할 수 있어요. read 트랜잭션은 복제본에서 일어날 수 있고, 다른 read 트랜잭션과 병렬로 동작할 수 있어요.
deferred BEGIN DEFERRED 트랜잭션이 read 모드로 시작했다가, write 문장이 실행되는 즉시 write로 바뀌어요. primary에서 write 트랜잭션이 실행 중이면 이 모드 전환이 실패할 수 있어요.

Batch Transactions

배치는 암묵적 트랜잭션 안에서 여러 SQL 문을 순차적으로 실행하는 거예요. 백엔드가 트랜잭션을 처리해요: 성공하면 모든 변경이 커밋되고, 실패하면 수정 없이 완전히 롤백돼요.

const result = await client.batch(
  [
    {
      sql: "INSERT INTO users VALUES (?)",
      args: ["Iku"],
    },
    {
      sql: "INSERT INTO users VALUES (?)",
      args: ["Iku 2"],
    },
  ],
  "write",
);

Interactive Transactions

SQLite의 인터랙티브 트랜잭션은 트랜잭션 범위 안에서 일련의 읽기·쓰기 작업의 일관성을 보장해요. 커밋이나 롤백 시점을 직접 제어할 수 있고, 다른 클라이언트 활동으로부터 격리되죠.

Method Description
execute() execute()와 같지만 트랜잭션 컨텍스트 안에서 실행돼요
commit() 트랜잭션의 모든 write 문장을 커밋해요
rollback() 트랜잭션 전체를 롤백해요
close() 트랜잭션을 즉시 중단해요

Rollback Usage

try {
  const userId = "user123";
  const withdrawalAmount = 500;

  const transaction = await client.transaction("write");

  const balanceResult = await transaction.execute({
    sql: "SELECT balance FROM accounts WHERE userId = ?",
    args: [userId],
  });

  const currentBalance = balanceResult.rows[0]["balance"] as number;

  if (currentBalance >= withdrawalAmount) {
    await transaction.execute({
      sql: "UPDATE accounts SET balance = balance - ? WHERE userId = ?",
      args: [withdrawalAmount, userId],
    });
  } else {
    console.log("Insufficient funds");
    await transaction.rollback();
    return;
  }

  await transaction.commit();
} catch (e) {
  console.error(e);
}

Batch Usage

let transaction: Transaction | null = null;

try {
  const records = [
    { name: "Alice", age: 30 },
    { name: "Bob", age: 25 },
    { name: "Charlie", age: 35 },
  ];

  transaction = await client.transaction("write");

  for (const record of records) {
    await transaction.execute({
      sql: "INSERT INTO people (name, age) VALUES (?, ?)",
      args: [record.name, record.age],
    });
  }

  await transaction.commit();
} catch (e) {
  console.error(e);
  if (transaction) await transaction.rollback();
}

Conditional Usage

try {
  const productId = "prod456";
  const newPrice = 150;

  const transaction = await client.transaction("write");

  const productResult = await transaction.execute({
    sql: "SELECT price FROM products WHERE productId = ?",
    args: [productId],
  });

  const currentPrice = productResult.rows[0]["price"] as number;

  if (currentPrice > newPrice) {
    await transaction.execute({
      sql: "UPDATE products SET price = ? WHERE productId = ?",
      args: [newPrice, productId],
    });
  } else {
    console.log("New price is not lower than current price");
    await transaction.rollback();
    return;
  }

  await transaction.commit();
} catch (e) {
  console.error(e);
}

주의할 점은, libSQL의 인터랙티브 트랜잭션은 커밋되거나 롤백될 때까지 데이터베이스에 쓰기 잠금을 걸며, 타임아웃은 5초예요. 지연 시간이 높거나 바쁜 데이터베이스에서는 성능에 영향을 줄 수 있어요.

ATTACH

ATTACH 문으로 현재 커넥션에 여러 데이터베이스를 붙일 수 있어요:

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

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

const txn = await db.transaction("read");

await txn.execute('ATTACH "<database-id>" AS attached');

const rs = await txn.execute("SELECT * FROM attached.users");

참고로, ATTACH 허용 설정과 데이터베이스를 attach할 권한이 있는 토큰 생성을 잊지 마세요 — 자세히 보기

더 알아보기 (Learn more)