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

TS 예제

원문 보기 위키 갱신

TS 예제 (Examples)

@libsql/client로 바로 돌려 볼 수 있는 TypeScript 예제 모음이에요. 배치 처리, 대화형 셸, 임베디드 레플리카 동기화, 벡터 검색까지 레시피 형태로 정리되어 있어요.

출처: 문서

본문

이 페이지의 예제는 모두 libsql-client-ts 리포지토리의 packages/libsql-client/examples 디렉터리에 있는 실행 가능한 스크립트예요. 클라이언트 패키지(@libsql/client)와 함께 제공되는 샘플이니까 클론해서 그대로 실행해 볼 수 있어요.

기본 예제: 배치와 파라미터 바인딩

테이블을 만들고 배치로 여러 SQL을 한 번에 실행한 다음, 위치 기반과 이름 붙은 플레이스홀더를 각각 쓰는 예제예요. example.js 파일 전체예요:

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

async function example() {
    const config = {
        url: process.env.URL ?? "file:local.db",
        encryptionKey: process.env.ENCRYPTION_KEY,
    };
    const db = createClient(config);
    await db.batch(
        [
            "CREATE TABLE IF NOT EXISTS users (email TEXT)",
            "INSERT INTO users (email) VALUES ('[email protected]')",
            "INSERT INTO users (email) VALUES ('[email protected]')",
        ],
        "write",
    );

    await db.batch(
        [
            {
                sql: "INSERT INTO users (email) VALUES (?)",
                args: ["[email protected]"],
            },
            ["INSERT INTO users (email) VALUES (?)", ["[email protected]"]],
            {
                sql: "INSERT INTO users (email) VALUES (:email)",
                args: { email: "[email protected]" },
            },
        ],
        "write",
    );

    const rs = await db.execute("SELECT * FROM users");
    console.log(rs);
}

await example();

연결 설정에서 URL은 환경 변수 URL로, 없으면 file:local.db를 써요. encryptionKey도 환경 변수로 넘길 수 있죠. 배치 안에서는 문자열 배열, sql/args 객체, [sql, args] 배열 세 가지 형태가 모두 허용되고, 위치 플레이스홀더 ?와 이름 붙은 플레이스홀더 :email을 섞어 쓸 수 있다는 점을 확인하세요.

대화형 SQL 셸

명령줄 인자로 받은 데이터베이스 URL에 접속해서 표준 입력으로 SQL을 계속 입력받아 실행하는 셸 예제예요. shell.js 파일 전체예요:

import * as readline from "node:readline/promises";
import { stdin, stdout, argv } from "node:process";
import * as libsql from "@libsql/client";

async function main() {
    const url = argv[2];
    if (!url) {
        console.error("Please specify database URL as command-line argument");
        return;
    }

    const client = libsql.createClient({ url });
    const rl = readline.createInterface({ input: stdin, output: stdout });

    for (;;) {
        const sql = await rl.question("> ");

        let rs;
        try {
            rs = await client.execute(sql);
        } catch (e) {
            if (e instanceof libsql.LibsqlError) {
                console.error(e);
                continue;
            }
            throw e;
        }

        console.log(JSON.stringify(rs.columns));
        for (const row of rs.rows) {
            console.log(JSON.stringify(Array.from(row)));
        }
    }
}

await main();

에러가 libsql.LibsqlError면 메시지를 출력하고 계속 진행해요. 그 외의 예외는 그대로 던져서 프로세스가 멈추게 두죠. 결과는 컬럼 이름 목록과 각 행을 JSON으로 출력해요.

임베디드 레플리카 동기화

로컬 파일 데이터베이스를 syncUrl에 연결하고, sync()를 호출할 때마다 프라이머리와 맞추는 예제예요. 방명록(guest book)에 코멘트를 남기는 흐름으로 구성되어 있어요. sync.js 파일 전체예요:

import { createClient } from "@libsql/client";
import reader from "readline-sync";

async function example() {
    const config = {
        url: process.env.URL ?? "file:local.db",
        syncUrl: process.env.SYNC_URL,
        authToken: process.env.AUTH_TOKEN,
    };
    const db = createClient(config);
    await db.sync();
    await db.execute(
        "CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)",
    );
    const rep = await db.sync();

    console.log("frames_synced: " + rep.frames_synced);

    const comment = reader.question("Enter your comment: ");

    await db.execute({
        sql: "INSERT INTO guest_book_entries (comment) VALUES (?)",
        args: [comment],
    });

    const rep2 = await db.sync();

    console.log("frames_synced: " + rep2.frames_synced);

    console.log("Guest book entries:");
    const rs = await db.execute("SELECT * FROM guest_book_entries");
    for (const row of rs.rows) {
        console.log(" - " + row.comment);
    }
}

example();

sync()가 반환하는 리포트의 frames_synced로 이번 동기화에서 몇 프레임을 가져왔는지 확인할 수 있어요.

오프라인 우선 동기화

offline: true 옵션을 켠 동기화 예제예요. 연결이 없어도 로컬 데이터베이스에서 바로 읽고 쓸 수 있다는 점이 위 예제와 달라요. sync_offline.js 파일 전체예요:

import { createClient } from "@libsql/client";
import reader from "readline-sync";

async function example() {
    const config = {
        url: process.env.URL ?? "file:local.db",
        syncUrl: process.env.SYNC_URL,
        authToken: process.env.AUTH_TOKEN,
        offline: true,
    };

    const db = createClient(config);

    console.log("Syncing database ...");
    await db.sync();

    await db.execute(
        "CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)",
    );

    const comment = reader.question("Enter your comment: ");

    await db.execute({
        sql: "INSERT INTO guest_book_entries (comment) VALUES (?)",
        args: [comment],
    });

    console.log("Syncing database ...");
    const rep2 = await db.sync();

    console.log("frames_synced: " + rep2.frames_synced);

    console.log("Guest book entries:");
    const rs = await db.execute("SELECT * FROM guest_book_entries");
    for (const row of rs.rows) {
        console.log(" - " + row.comment);
    }
}

example();

벡터 검색과 함께 동기화

동기화되는 데이터베이스에서 벡터 임베딩을 저장하고 KNN(최근접 이웃) 검색을 하는 예제예요. 영화 제목과 4차원 임베딩을 저장하고, 벡터 인덱스를 만들어 유사한 영화를 찾아요. sync_vector.js 파일 전체예요:

import { createClient } from "@libsql/client";
import reader from "readline-sync";

async function example() {
    const config = {
        url: process.env.URL ?? "file:local.db",
        syncUrl: process.env.SYNC_URL,
        authToken: process.env.AUTH_TOKEN,
    };
    const db = createClient(config);
    await db.sync();
    await db.execute(
        "CREATE TABLE IF NOT EXISTS movies (title TEXT, embedding FLOAT32(4))",
    );
    await db.execute(
        "CREATE INDEX IF NOT EXISTS movies_idx ON movies (libsql_vector_idx(embedding))",
    );
    await db.sync();

    const title = reader.question("Add movie (title): ");
    const embedding = reader.question(
        "Add movie (embedding, e.g. [1,2,3,4]): ",
    );

    await db.execute({
        sql: "INSERT INTO movies (title, embedding) VALUES (?, vector32(?))",
        args: [title, embedding],
    });

    await db.sync();

    const all = await db.execute(
        "SELECT title, vector_extract(embedding) as embedding FROM movies",
    );
    console.info("all movies:");
    for (const row of all.rows) {
        console.log(" - " + row.title + ": " + row.embedding);
    }

    const query = reader.question("KNN query (e.g. [1,2,3,4]): ");
    const nn = await db.execute({
        sql: "SELECT title, vector_extract(embedding) as embedding FROM vector_top_k('movies_idx', vector32(?), 2) as knn JOIN movies ON knn.id = movies.rowid",
        args: [query],
    });
    console.info("nearest neighbors:");
    for (const row of nn.rows) {
        console.log(" - " + row.title + ": " + row.embedding);
    }
}

example();

인덱스를 만들 때 libsql_vector_idx(embedding)을 쓰고, 검색 때는 vector_top_k('movies_idx', vector32(?), 2)로 상위 2개 이웃을 가져오는 흐름을 확인하세요.

예제 실행에 쓰는 package.json

예제 디렉터리의 package.json도 함께 참고하세요:

{
    "name": "libsql-examples",
    "type": "module",
    "private": true,
    "dependencies": {
        "@libsql/client": "..",
        "@libsql/core": "../../libsql-core",
        "readline-sync": "^1.4.10"
    }
}

더 알아보기 (Learn more)

이 페이지는 docs.turso.tech가 GitHub 리포지토리로 리다이렉트하는 페이지예요. 실행 가능한 최신 예제 코드는 libsql-client-ts 리포지토리의 examples 디렉터리에서, Turso 문서 전체 색인은 llms.txt에서 확인할 수 있어요.