Skip to main content

TransactionContext middlewares

Initial configuration​

To begin using the transaction middlewares, you'll need to create and configure a TransactionContext instance:

./samples/transaction-context.ts
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 "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("DATABASE_NAME.db"),
}),
});

const executionContext = new ExecutionContext(new AlsExecutionContextAdapter());

export const transactionContext = new TransactionContext<Kysely<any>>({
token: contextToken("transaction"),
adapter: new KyselyTransactionAdapter({ database }),
executionContext,
});

withTransactionFactory middleware​

The transaction middleware intercepts function calls and runs them inside a transaction. When the wrapped function is invoked, the middleware delegates to transactionContext.run() with the requested propagation mode, so the wrapped function executes within a transaction scope — joining, starting, or forbidding one depending on the mode.

The propagation mode defaults to REQUIRED: the wrapped function joins an existing transaction when one is active and starts a new one otherwise.

Usage​

./samples/with-transaction.ts
import { use } from "eridu-tech/middleware";
import { TRANSACTION_PROPAGATION } from "eridu-tech/transaction-context/contracts";
import { withTransactionFactory } from "eridu-tech/transaction-context/middlewares";
import { transactionContext } from "./transaction-context.js";

const withTransaction = withTransactionFactory(transactionContext);

const createUser = async (userId: string, name: string): Promise<string> => {
await transactionContext.current
.insertInto("users")
.values({ id: userId, name })
.execute();
return userId;
};

// Runs the wrapped function inside a transaction, using REQUIRED propagation by default
const createUserInTransaction = use(createUser, withTransaction());

// Runs the wrapped function inside the existing transaction
const createUserInExistingTransaction = use(
createUser,
withTransaction(TRANSACTION_PROPAGATION.MANDATORY),
);

await createUserInTransaction("1", "Jose");
info

Here is a complete list of settings for the withTransactionFactory function.

withAfterCommitFactory middleware​

The after-commit middleware intercepts function calls and registers them to run once the active transaction commits. When the wrapped function is invoked, the middleware delegates to transactionContext.afterCommit(), so the wrapped function is registered as an after-commit invocable instead of running immediately. This is useful for side effects that must not happen when the transaction is rolled back, such as sending emails or publishing events.

When no transaction is active, the wrapped function runs immediately, unless runIfNoTransaction is false, in which case it is discarded.

Usage​

./samples/with-after-commit.ts
import { use } from "eridu-tech/middleware";
import { withAfterCommitFactory } from "eridu-tech/transaction-context/middlewares";
import { transactionContext } from "./transaction-context.js";

const withAfterCommit = withAfterCommitFactory(transactionContext);

const sendWelcomeEmail = async (userId: string): Promise<void> => {
// ...
};

// Registers the wrapped function to run once the active transaction commits
const sendWelcomeEmailAfterCommit = use(sendWelcomeEmail, withAfterCommit());

const sendWelcomeEmailAfterCommitOrSkip = use(
sendWelcomeEmail,
// Discards the wrapped function when no transaction is active
withAfterCommit({ runIfNoTransaction: false }),
);

// Runs once the transaction commits
await transactionContext.run(() => sendWelcomeEmailAfterCommit("1"));

// Does nothing, because no transaction is active
await sendWelcomeEmailAfterCommitOrSkip("2");
info

Here is a complete list of settings for the withAfterCommitFactory function.

Settings​

OptionTypeDefaultDescription
runIfNoTransactionbooleantrueWhether to invoke the wrapped function immediately when there is no active transaction, instead of discarding it

Further information​

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