n8n-io/n8n · error · TypeORMError

Transaction method requires callback in second parameter if

Error message

Transaction method requires callback in second parameter if isolation level is supplied.

What it means

EntityManager.transaction() accepts an overload where the first argument is an IsolationLevel string and the second is the callback. If callers pass an isolation level but no callback, the runInTransaction variable is undefined and TypeORM throws a TypeORMError explaining that the callback is required when an isolation level is supplied. This is a usage contract guard, not a runtime/data condition.

Source

Thrown at packages/@n8n/typeorm/src/entity-manager/EntityManager.ts:130

	): Promise<T>;

	/**
	 * Wraps given function execution (and all operations made there) in a transaction.
	 * All database operations must be executed using provided entity manager.
	 */
	async transaction<T>(
		isolationOrRunInTransaction: IsolationLevel | ((entityManager: EntityManager) => Promise<T>),
		runInTransactionParam?: (entityManager: EntityManager) => Promise<T>,
	): Promise<T> {
		const isolation =
			typeof isolationOrRunInTransaction === 'string' ? isolationOrRunInTransaction : undefined;
		const runInTransaction =
			typeof isolationOrRunInTransaction === 'function'
				? isolationOrRunInTransaction
				: runInTransactionParam;

		if (!runInTransaction) {
			throw new TypeORMError(
				`Transaction method requires callback in second parameter if isolation level is supplied.`,
			);
		}

		if (this.queryRunner && this.queryRunner.isReleased)
			throw new QueryRunnerProviderAlreadyReleasedError();

		// if query runner is already defined in this class, it means this entity manager was already created for a single connection
		// if its not defined we create a new query runner - single connection where we'll execute all our operations
		const queryRunner = this.queryRunner || this.connection.createQueryRunner();

		try {
			await queryRunner.startTransaction(isolation);
			const result = await runInTransaction(queryRunner.manager);
			await queryRunner.commitTransaction();
			return result;
		} catch (err) {
			try {

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Pass the callback as the second argument: `manager.transaction('SERIALIZABLE', async (em) => { ... })`.
  2. If you don't need a custom isolation level, drop the first argument entirely: `manager.transaction(async (em) => { ... })`.
  3. If building the call dynamically, assert the callback is a function before invoking, and enable strict TS so the overload rejects missing args.
  4. Add a unit test asserting transaction() is always invoked with both arguments at that call site.

Example fix

// before
await manager.transaction('SERIALIZABLE');

// after - callback supplied as second argument
await manager.transaction('SERIALIZABLE', async (em) => {
  await em.save(User, { id: 1, name: 'x' });
});
Defensive patterns

Strategy: validation

Validate before calling

function assertTransactionArgs(isolation: unknown, cb: unknown): asserts cb is Function {
  if (typeof isolation === 'string' && typeof cb !== 'function') {
    throw new Error('manager.transaction(isolation, cb): cb is required when isolation is supplied');
  }
}
// usage
assertTransactionArgs(isolationLevel, callback);
await manager.transaction(isolationLevel as IsolationLevel, callback);

Prevention

When it happens

Trigger: Calling `manager.transaction('SERIALIZABLE')` with no second argument; passing an isolation string and accidentally omitting the callback; refactoring a call site that previously passed only a callback and adding an isolation level without updating argument order; programmatically building the call and forgetting the callback slot.

Common situations: Refactor across TypeORM versions (older code had different overloads); copy-paste from examples that omit the callback; TS strictness bypassed via `any` so the compiler did not catch the missing argument; dead/conditional callback that evaluated to undefined.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/ae62732841337037. Report an issue: GitHub.