Skip to content
// EFFECT DAYS 2026

SqlClient unstable

Main SQL client service for tagged-template queries.

SqlClient combines the tagged-template statement constructor with connection acquisition, dialect compilation, transactions, row transforms, tracing, and reactive query helpers. Driver integrations build this service from their connection and compiler pieces.

11 exports Since v4.0.0 Source

Constructors

make

unstable
Since v4.0.0

Constructs a SqlClient from connection acquirers, a compiler, transaction commands, tracing attributes, optional row transforms, and reactive query integration.

Signature

declare const make: (...args: [options: MakeOptions]) => Effect<SqlClient, never, Reactivity>
Since v4.0.0

Builds the acquirers, borrower and failed-COMMIT cleanup for a SQLite client that shares one connection through a semaphore.

Details

Both acquirers lease the connection into the current scope and hold the semaphore until that scope closes. The borrower holds the same semaphore until the statement finishes without creating a scope. Another fiber cannot begin a transaction underneath either. Effects that use the connection directly, such as an export, have to run inside Effect.scoped.

SQLite can keep a transaction open after COMMIT fails, for example on a deferred foreign key violation. onCommitFailure rolls it back before the connection is reused. If that ROLLBACK fails, the connection is rejected, and each acquisition or borrow retries the ROLLBACK once while holding the semaphore until it succeeds. The database is never reopened, and transactions started with a plain BEGIN are only touched after a failed cleanup.

Pass isTransaction when the driver can report whether a transaction is open. Without it, ROLLBACK always runs and SQLite's "no transaction is active" error counts as clean.

Signature

declare function makeSqliteAcquirers<C extends Connection>(options: {
readonly connection: Effect<C, SqlError>;
readonly isTransaction?: (conn: C) => boolean;
readonly semaphore: Semaphore;
}): {
readonly acquirer: Effect<C, SqlError, Scope>;
readonly borrower: Borrower;
readonly isTransaction: (conn: Connection) => boolean | undefined;
readonly onCommitFailure: (conn: Connection) => Effect<void, SqlError>;
readonly transactionAcquirer: Effect<C, SqlError, Scope>;
}

Models

SqlClient

interface unstable
Since v4.0.0

SQL client service interface, combining the statement constructor API with connection reservation, transaction handling, and reactive query helpers.

Signature

interface SqlClient extends Constructor {
<A extends object = Row>(strings: TemplateStringsArray, ...args: Array<any>): Statement<A>;
(value: string): Identifier;
readonly "~effect/sql/SqlClient": "~effect/sql/SqlClient";
readonly afterCommit: <R>(effect: Effect<void, never, R>) => Effect<void, never, R>;
readonly driver: string;
readonly reactive: <A, E, R>(keys: readonly Array<unknown> | ReadonlyRecord<string, readonly Array<unknown>>, effect: Effect<A, E, R>) => Stream<A, E, R>;
readonly reactiveMailbox: <A, E, R>(keys: readonly Array<unknown> | ReadonlyRecord<string, readonly Array<unknown>>, effect: Effect<A, E, R>) => Effect<Dequeue<A, E>, never, Scope | R>;
readonly reserve: Effect<Connection, SqlError, Scope>;
readonly safe: SqlClient;
readonly transactionService: Service<TransactionConnection, Service>;
readonly withoutTransforms: () => this;
readonly withTransaction: <R, E, A>(self: Effect<A, E, R>) => Effect<A, SqlError | E, R>;
}

TransactionConnection

interface unstable
Since v4.0.0

Phantom identifier for the scoped transaction connection service associated with a SQL client.

Signature

interface TransactionConnection {
readonly _: typeof _;
}

Other

SqlClient

unstable
Since v4.0.0

Namespace containing types associated with the SqlClient service.

Since v4.0.0

Namespace containing types associated with transaction connection services.

Services

Since v4.0.6

Context reference holding the statement that starts a top-level transaction in place of the client's BEGIN; defaults to undefined, which keeps the client's.

When to use

Use when one transaction needs its own isolation level or access mode, as in BEGIN ISOLATION LEVEL SERIALIZABLE on PostgreSQL, rather than following the client's BEGIN with a SET TRANSACTION, which costs another round trip.

Details

The statement runs unprepared. Nested transactions ignore it and keep their savepoints.

Gotchas

It applies to every top-level transaction started while it is provided, including in forked fibers, so provide it around the single withTransaction it is meant for. @effect/sql-libsql, @effect/sql-mssql and @effect/sql-sqlite-do don't start transactions with a SQL statement and ignore it. The statement is sent as raw SQL, so never build it from untrusted input.

Signature

declare const BeginTransaction: Reference<string | undefined>

Example Starting a serializable transaction

import { Effect } from "effect"
import { SqlClient } from "effect/sql"
const program = Effect.gen(function*() {
const sql = yield* SqlClient.SqlClient
return yield* sql.withTransaction(sql`SELECT 1`).pipe(
Effect.provideService(SqlClient.BeginTransaction, "BEGIN ISOLATION LEVEL SERIALIZABLE")
)
})
Since v4.0.0

Context reference used by SQL integrations to opt in to safe integer handling; defaults to false.

Signature

declare const SafeIntegers: Reference<boolean>

SqlClient

unstable
Since v4.0.0

Service tag for the active SQL client service.

When to use

Use to access or provide the SQL client used to build statements, stream rows, reserve connections, and run transactions.

Signature

declare const SqlClient: Service<SqlClient, SqlClient>
Since v4.0.0

Creates a unique context service tag for the active transaction connection of a specific SQL client.

Signature

declare const TransactionConnection: (clientId: number) => Service<TransactionConnection, Service>

Transactions

Since v4.0.0

Builds a transaction wrapper that begins top-level transactions, uses savepoints for nested transactions, commits on success, and rolls back on failure or interruption. Releases nested savepoints when releaseSavepoint is provided.

Details

Transactions started this way are also recognized by SqlResolver when the caller's context has no SqlClient, so their requests don't share a batch with requests outside the transaction.

Each callback below except acquireConnection is traced like a statement, in its own client span under the sql.transaction span, named after the operation and the target derived from spanAttributes, such as COMMIT mydb. With Statement.SpanPropagationEnabled, a top-level transaction also records how long acquireConnection took as db.client.connection.wait_time_ms, so work a driver does there, such as starting the transaction, counts as waiting for the connection.

Signature

declare function makeWithTransaction<I, S>(options: {
readonly acquireConnection: (restore: <XA, XE, XR>(effect: Effect<XA, XE, XR>) => Effect<XA, XE, XR>) => Effect<S, SqlError, Scope>;
readonly begin: (conn: NoInfer<S>, statement: string | undefined) => Effect<void, SqlError>;
readonly commit: (conn: NoInfer<S>) => Effect<void, SqlError>;
readonly onCommitFailure?: (conn: NoInfer<S>) => Effect<void, SqlError>;
readonly releaseSavepoint?: (conn: NoInfer<S>, id: number) => Effect<void, SqlError>;
readonly rollback: (conn: NoInfer<S>) => Effect<void, SqlError>;
readonly rollbackSavepoint: (conn: NoInfer<S>, id: number) => Effect<void, SqlError>;
readonly savepoint: (conn: NoInfer<S>, id: number) => Effect<void, SqlError>;
readonly spanAttributes: readonly Array<readonly [string, unknown]>;
readonly transactionService: Key<I, readonly [S, number]>;
}): <R, E, A>(effect: Effect<A, E, R>) => Effect<A, SqlError | E, R>