Configuring TransactionContext adapters
KyselyTransactionAdapter
To use the KyselyTransactionAdapter, you'll need to install the required dependency: kysely package.
Usage with Sqlite
You will need to install better-sqlite3 package:
import { KyselyTransactionAdapter } from "eridu-tech/transaction-context/kysely-transaction-adapter";
import Sqlite from "better-sqlite3";
import { Kysely, SqliteDialect } from "kysely";
const database = new Kysely<any>({
dialect: new SqliteDialect({
database: new Sqlite(":memory:"),
}),
});
export const kyselyTransactionAdapter = new KyselyTransactionAdapter({
database,
});
Usage with Postgres
You will need to install pg package:
import { KyselyTransactionAdapter } from "eridu-tech/transaction-context/kysely-transaction-adapter";
import { Kysely, PostgresDialect } from "kysely";
import { Pool } from "pg";
export const database = new Kysely<any>({
dialect: new PostgresDialect({
pool: new Pool({
database: "DATABASE_NAME",
host: "DATABASE_HOST",
user: "DATABASE_USER",
// DATABASE port
port: 5432,
password: "DATABASE_PASSWORD",
max: 10,
}),
}),
});
export const kyselyTransactionAdapter = new KyselyTransactionAdapter({
database,
});
Usage with Mysql
You will need to install mysql2 package. The same setup applies to MariaDB, by using MysqlDialect:
import { KyselyTransactionAdapter } from "eridu-tech/transaction-context/kysely-transaction-adapter";
import { createPool } from "mysql2";
import { Kysely, MysqlDialect } from "kysely";
const database = new Kysely<any>({
dialect: new MysqlDialect({
pool: createPool({
host: "DATABASE_HOST",
// Database port
port: 3306,
database: "DATABASE_NAME",
user: "DATABASE_USER",
password: "DATABASE_PASSWORD",
connectionLimit: 10,
}),
}),
});
export const kyselyTransactionAdapter = new KyselyTransactionAdapter({
database,
});
Usage with Libsql
You will need to install @libsql/kysely-libsql package:
import { KyselyTransactionAdapter } from "eridu-tech/transaction-context/kysely-transaction-adapter";
import { LibsqlDialect } from "@libsql/kysely-libsql";
import { Kysely } from "kysely";
const database = new Kysely<any>({
dialect: new LibsqlDialect({
url: "DATABASE_URL",
}),
});
export const kyselyTransactionAdapter = new KyselyTransactionAdapter({
database,
});
Usage with other databases
Note kysely has support for multiple databases.
Before choosing a database, ensure it supports transactions. Without transaction support,
starting a transaction fails and a StartTransactionError is thrown.
Settings
import { KyselyTransactionAdapter } from "eridu-tech/transaction-context/kysely-transaction-adapter";
import { database } from "./kysely-transaction-postgres.js";
export const kyselyTransactionAdapter = new KyselyTransactionAdapter({
database,
// Applied to every new transaction
// This is the default value
accessMode: "read write",
// Applied to every new transaction
// This is the default value
isolationLevel: "serializable",
});
Kysely's SQLite driver ignores the configured access mode and isolation level, because SQLite does not support them. Other dialects apply both settings to every new transaction.
Usage with TransactionContext
Pass the adapter to the TransactionContext class, then read current to get the client of the current scope. Inside run() it is the transaction-scoped client, outside of it the base client:
import { ExecutionContext } from "eridu-tech/execution-context";
import { AlsExecutionContextAdapter } from "eridu-tech/execution-context/als-execution-context-adapter";
import { contextToken } from "eridu-tech/execution-context/contracts";
import { TransactionContext } from "eridu-tech/transaction-context";
import { kyselyTransactionAdapter } from "./kysely-transaction-sqlite.js";
import type { Kysely } from "kysely";
const executionContext = new ExecutionContext(new AlsExecutionContextAdapter());
export const transactionContext = new TransactionContext<Kysely<any>>({
token: contextToken("kysely-transaction"),
adapter: kyselyTransactionAdapter,
executionContext,
});
async function createUser(userId: string): Promise<void> {
// `current` is the transaction-scoped client when a transaction is active,
// otherwise the base client
await transactionContext.current
.insertInto("users")
.values({ id: userId })
.execute();
}
// No transaction is active, so `current` is the base client
await createUser("1");
await transactionContext.run(async () => {
// A transaction is active, so the same call runs inside it
await createUser("2");
});
MongodbTransactionAdapter
To use the MongodbTransactionAdapter, you'll need to install the required dependency: mongodb package.
The client setting is the MongoClient used to start sessions and transactions, while database is the Db instance exposed as the base (non-transactional) client:
import { MongodbTransactionAdapter } from "eridu-tech/transaction-context/mongodb-transaction-adapter";
import { MongoClient } from "mongodb";
// The client used to start sessions and transactions
const mongoClient = new MongoClient("mongodb://localhost:27017");
// The database exposed as the base (non-transactional) client
const database = mongoClient.db("DATABASE_NAME");
export const mongodbTransactionAdapter = new MongodbTransactionAdapter({
client: mongoClient,
database,
});
MongoDB only supports transactions on replica sets and sharded clusters. Standalone deployments will fail as soon as an operation runs inside a transaction.
Settings
import { TimeSpan } from "eridu-tech/time-span";
import { MongodbTransactionAdapter } from "eridu-tech/transaction-context/mongodb-transaction-adapter";
import { MongoClient } from "mongodb";
const mongoClient = new MongoClient("mongodb://localhost:27017");
export const mongodbTransactionAdapter = new MongodbTransactionAdapter({
client: mongoClient,
database: mongoClient.db("DATABASE_NAME"),
// Applied when committing a transaction
commitTimeout: TimeSpan.fromSeconds(10),
// Applied when aborting a transaction
abortTimeout: TimeSpan.fromSeconds(10),
// Passed to `startTransaction` for each new transaction
startTransactionSettings: {
readConcern: { level: "snapshot" },
readPreference: "primary",
writeConcern: { w: "majority" },
},
// Passed to `startSession` when a new session is created
startSessionSettings: {
causalConsistency: true,
},
// Passed to `endSession` after a transaction is committed or aborted
endSessionSettings: {},
});
Every transaction commits or aborts on its own session, and the session is always ended afterwards — even when committing or aborting fails.
Usage with TransactionContext
MongoDB scopes a transaction to a ClientSession, so read transaction to get the session of the active transaction and pass it to the operations that must join it. client always stays the base database, which must not be used for transactional work:
import { ExecutionContext } from "eridu-tech/execution-context";
import { AlsExecutionContextAdapter } from "eridu-tech/execution-context/als-execution-context-adapter";
import { contextToken } from "eridu-tech/execution-context/contracts";
import { TransactionContext } from "eridu-tech/transaction-context";
import { mongodbTransactionAdapter } from "./mongodb-transaction-adapter-settings.js";
import type { ClientSession, Db } from "mongodb";
const executionContext = new ExecutionContext(new AlsExecutionContextAdapter());
export const transactionContext = new TransactionContext<Db, ClientSession>({
token: contextToken("mongodb-transaction"),
adapter: mongodbTransactionAdapter,
executionContext,
});
// `client` is always the base database, it never becomes transaction-scoped
const users = transactionContext.client.collection("users");
async function createUser(name: string): Promise<void> {
// Every operation must be given the session to join the active transaction
await users.insertOne(
{
name,
},
{
// The session of the active transaction, or `undefined` outside one
session: transactionContext.transaction ?? undefined,
},
);
}
// No transaction is active, so the insert runs on the base database
await createUser("Jose");
await transactionContext.run(async () => {
// A transaction is active, so the insert joins it
await createUser("Jose");
});
Further information
For further information refer to eridu-tech/transaction-context API docs.