Skip to main content

DI Integration

The ProxyTransactionContextResolver class provides an ITransactionContextResolver whose underlying resolver is resolved from a DI container.

The proxy supports the ITransactionContextResolver operations:

  • use(adapterName?) returns a transaction runner that resolves the resolver from the container for each operation.
  • run(...) delegates to the default adapter's transaction runner.
  • afterCommit(asyncInvocable, settings?) resolves the resolver from the container and delegates to it.

The proxy resolves the resolver lazily, so it supports the container's configured lifetimes. Scoped registrations must be used inside container.run(). Initialize the resolver from the container before using it.

Initial configuration​

Set up the MongoDB and SQLite databases and transaction adapters. Create the container, register the MongoDB adapter as primary (default) and the SQLite adapter as secondary, initialize the resolver from its onInit hook, and wrap it with ProxyTransactionContextResolver:

1. Set up the MongoDB database​

./samples/mongodb-database.ts
import { MongoClient } from "mongodb";

export const mongoClient = new MongoClient("mongodb://localhost:27017");
export const mongoDatabase = mongoClient.db("DATABASE_NAME");

2. Create the MongoDB transaction adapter​

./samples/mongodb-transaction-adapter.ts
import { MongodbTransactionAdapter } from "eridu-tech/transaction-context/mongodb-transaction-adapter";
import { mongoClient, mongoDatabase } from "./mongodb-database.js";

export const mongodbTransactionAdapter = new MongodbTransactionAdapter({
client: mongoClient,
database: mongoDatabase,
});

3. Set up the SQLite database​

./samples/sqlite-database.ts
import Sqlite from "better-sqlite3";
import { Kysely, SqliteDialect } from "kysely";

export const sqliteDatabase = new Kysely<any>({
dialect: new SqliteDialect({ database: new Sqlite(":memory:") }),
});

4. Create the SQLite transaction adapter​

./samples/sqlite-transaction-adapter.ts
import { KyselyTransactionAdapter } from "eridu-tech/transaction-context/kysely-transaction-adapter";
import { sqliteDatabase } from "./sqlite-database.js";

export const sqliteTransactionAdapter = new KyselyTransactionAdapter({
database: sqliteDatabase,
});

5. Create the dependency injection container​

./samples/container.ts
import { Container } from "eridu-tech/di";
import { ExecutionContext } from "eridu-tech/execution-context";
import { AlsExecutionContextAdapter } from "eridu-tech/execution-context/als-execution-context-adapter";

export const executionContext = new ExecutionContext(
new AlsExecutionContextAdapter(),
);
export const container = new Container({ executionContext });

6. Register and initialize the transaction context resolver​

./samples/transaction-context-resolver-di.ts
import { genericToken, LIFETIME } from "eridu-tech/di/contracts";
import { ProxyTransactionContextResolver } from "eridu-tech/transaction-context/di";
import { TransactionContextResolver } from "eridu-tech/transaction-context";
import { container, executionContext } from "./container.js";
import { mongodbTransactionAdapter } from "./mongodb-transaction-adapter.js";
import { sqliteTransactionAdapter } from "./sqlite-transaction-adapter.js";

type Adapters = "primary" | "secondary";

const resolverToken = genericToken<TransactionContextResolver<Adapters>>(
"transactionContextResolver",
);

container.registerFactory({
token: resolverToken,
factory: () =>
new TransactionContextResolver<Adapters>({
adapters: {
primary: mongodbTransactionAdapter,
secondary: sqliteTransactionAdapter,
},
defaultAdapter: "primary",
executionContext,
}),
onInit: async (resolver) => {
await resolver.init();
},
deps: {},
lifetime: LIFETIME.SINGLETON,
});

// Create the proxy before container.init()
export const transactionContext = new ProxyTransactionContextResolver<Adapters>(
{
container,
resolverToken,
},
);

await container.init();

Usage​

1. Using the default adapter​

./samples/transaction-context-resolver-di-default-adapter.ts
import { transactionContext } from "./transaction-context-resolver-di.js";

// Runs inside a transaction using the default (primary) adapter
await transactionContext.run(async () => {
// Perform transactional work here
await transactionContext.afterCommit(async () => {
console.log("Transaction committed");
});
});

2. Specifying an adapter explicitly​

./samples/transaction-context-resolver-di-specific-adapter.ts
import { transactionContext } from "./transaction-context-resolver-di.js";

// Runs inside a transaction using the secondary adapter
await transactionContext.use("secondary").run(async () => {
// Perform transactional work here
});

Further information​

For further information refer to eridu-tech/transaction-context API docs.