Skip to main content

Overview

@effect/sql-libsql provides a libSQL driver for Effect’s SQL toolkit. libSQL is an open-source fork of SQLite developed by Turso, offering both local and remote database access with features like edge replication.

Installation

npm install @effect/sql-libsql

Basic Usage

Local Database

Create a local SQLite database:
import { LibsqlClient } from "@effect/sql-libsql"
import { Effect, Layer } from "effect"

const SqlLive = LibsqlClient.layer({
  url: "file:local.db"
})

In-Memory Database

For testing or temporary data:
const SqlLive = LibsqlClient.layer({
  url: "file::memory:"
})

Remote Database (Turso)

Connect to a Turso database:
import { Redacted } from "effect"

const SqlLive = LibsqlClient.layer({
  url: "libsql://your-database.turso.io",
  authToken: Redacted.make("your-auth-token")
})

Executing Queries

import { Effect } from "effect"
import { SqlClient } from "effect/unstable/sql"

const program = Effect.gen(function* () {
  const sql = yield* SqlClient.SqlClient

  // Simple query
  const users = yield* sql<{ id: number; name: string }>`
    SELECT id, name FROM users WHERE active = ${true}
  `

  // Insert with helper
  yield* sql`
    INSERT INTO users ${sql.insert({ name: "Alice", email: "[email protected]" })}
  `

  // Update with helper
  yield* sql`
    UPDATE users SET ${sql.update({ name: "Bob" })} WHERE id = ${1}
  `
})

const runnable = program.pipe(Effect.provide(SqlLive))

Configuration Options

Connection Options

OptionTypeDescription
urlstring | URLDatabase URL (file:, libsql:, http:, https:, ws:, wss:)
authTokenRedactedAuthentication token for remote databases
encryptionKeyRedactedEncryption key for SQLite encryption extension
tlsbooleanEnable/disable TLS for libsql: URLs (default: true)

Integer Handling

OptionTypeDescription
intMode"number" | "bigint" | "string"How to represent SQLite integers (default: “number”)

Concurrency

OptionTypeDescription
concurrencynumberMax concurrent requests (default: 20, 0 for unlimited)

Sync Options (Embedded Replicas)

OptionTypeDescription
syncUrlstring | URLURL of remote server to sync with
syncIntervalnumberSync interval in seconds

Transform Options

OptionTypeDescription
transformResultNames(str: string) => stringTransform column names in results
transformQueryNames(str: string) => stringTransform identifiers in queries

Advanced Options

OptionTypeDescription
spanAttributesRecord<string, unknown>Custom telemetry attributes

Supported URL Formats

libSQL supports multiple URL formats:
ProtocolDescriptionExample
file:Local SQLite databasefile:local.db
file::memory:In-memory databasefile::memory:
libsql:Turso/libSQL protocollibsql://db.turso.io
http: / https:HTTP connectionhttps://db.turso.io
ws: / wss:WebSocket connectionwss://db.turso.io

Embedded Replicas

Combine local and remote databases for edge computing:
const SqlLive = LibsqlClient.layer({
  url: "file:local-replica.db",
  syncUrl: "libsql://your-database.turso.io",
  authToken: Redacted.make("your-auth-token"),
  syncInterval: 60 // Sync every 60 seconds
})
This creates a local SQLite database that automatically syncs with a remote Turso database, providing low-latency reads and eventual consistency.

Integer Modes

Control how SQLite integers are represented in JavaScript:
// Use bigint for precise large integers
const SqlLive = LibsqlClient.layer({
  url: "file:local.db",
  intMode: "bigint"
})

// Use string to avoid precision loss
const SqlLive = LibsqlClient.layer({
  url: "file:local.db",
  intMode: "string"
})
With intMode: "number", integers larger than 2^53-1 will throw a RangeError.

Encryption

Use SQLite encryption extension:
import { Redacted } from "effect"

const SqlLive = LibsqlClient.layer({
  url: "file:encrypted.db",
  encryptionKey: Redacted.make("your-encryption-key")
})

Transactions

Automatic transaction management:
const sql = yield* SqlClient.SqlClient

const result = yield* sql.withTransaction(
  Effect.gen(function* () {
    yield* sql`INSERT INTO accounts (balance) VALUES (${100})`
    yield* sql`UPDATE accounts SET balance = balance - ${50} WHERE id = ${1}`
    return yield* sql<{ balance: number }>`SELECT balance FROM accounts WHERE id = ${1}`
  })
)
Transactions automatically roll back on errors and commit on success.

Name Transformations

Automatically convert between naming conventions:
import { String } from "effect"

const SqlLive = LibsqlClient.layer({
  url: "file:local.db",
  // Transform camelCase to snake_case for queries
  transformQueryNames: String.camelToSnake,
  // Transform snake_case to camelCase for results
  transformResultNames: String.snakeToCamel
})

// Use camelCase in your code
const users = yield* sql<{ userId: number; firstName: string }>`
  SELECT userId, firstName FROM users
`
// Queries become: SELECT user_id, first_name FROM users

Error Handling

All SQL errors are wrapped in SqlError:
import { SqlError } from "effect/unstable/sql"

const program = Effect.gen(function* () {
  const sql = yield* SqlClient.SqlClient
  return yield* sql`SELECT * FROM users`
}).pipe(
  Effect.catchTag("SqlError", (error) =>
    Effect.log(`Database error: ${error.message}`)
  )
)

Using with Existing Client

Wrap an existing libSQL client:
import { createClient } from "@libsql/client"

const client = createClient({
  url: "libsql://your-database.turso.io",
  authToken: "your-auth-token"
})

const SqlLive = LibsqlClient.layer({
  liveClient: client
})

Type Safety

The client is fully typed with Effect’s type system:
interface User {
  id: number
  name: string
  email: string
}

const users = yield* sql<User>`SELECT * FROM users`
// users: ReadonlyArray<User>

Turso Platform Features

When using Turso, you get additional features:
  • Edge deployment: Databases close to your users
  • Automatic replication: Multi-region data replication
  • Point-in-time recovery: Restore to any point in time
  • Branching: Create database branches like Git
  • REST API: Query via HTTP/REST
Learn more at turso.tech

Migration from SQLite

libSQL is compatible with SQLite. To migrate:
  1. Replace your SQLite driver import with @effect/sql-libsql
  2. Update the connection URL from file: format
  3. Optionally add authToken for remote databases
// Before (SQLite)
import { SqliteClient } from "@effect/sql-sqlite-node"
const SqlLive = SqliteClient.layer({ filename: "local.db" })

// After (libSQL)
import { LibsqlClient } from "@effect/sql-libsql"
const SqlLive = LibsqlClient.layer({ url: "file:local.db" })

Build docs developers (and LLMs) love