TypeOrm nested transaction behavior

# Postmortem: Unexpected Transaction Nesting Behavior in TypeORM

## Summary
During database operation implementation, nested transaction blocks (`transaction` inside `transaction`) were implemented assuming independent transactions would be created. Instead, TypeORM reused the outer transaction context, causing unintended state propagation and rollback behavior.

## Root Cause
Transaction nesting behavior in TypeORM exhibits a non-standard pattern:
- Nested transactions implement a **savepoint pattern** rather than creating new autonomous transactions
- Inner transactions operate within the same database connection as the outermost transaction
- Transaction contexts inherit leak-prone state (e.g., query runners, isolation levels) from parent scopes

## Why This Happens in Real Systems
- ORM abstraction hides database differences (PostgreSQL vs MySQL vs SQL Server)
- Legacy API designs transported across ORM versions produce inconsistent behavior
- Documentation blurring between "transaction" vs "savepoint" terminology
- Asynchronous control flow obscures context propagation chains

## Real-World Impact
- Partial rollbacks triggering unexpected full-transaction rollbacks
- Undetectable commit skew due to shared isolation levels
- Deadlock risks when nested scopes acquire conflicting locks
- Client-side state pollution via context-contaminated entity managers

## Example or Code

```typescript
await dataSource.transaction(async (tx1) => {
  await tx1.save(User, { id: 1, name: "Alice" });

  await tx1.transaction(async (tx2) => {
    // IN REALITY: Uses tx1's transaction context
    // Only creates a SAVEPOINT if supported by DB
    await tx2.save(Profile, { userId: 1, bio: "..." });

    // This rollback only rolls back to savepoint
    throw new Error("Inner failure");
  });

  // Outer transaction remains active but logically inconsistent
});
// Outer COMMIT executes despite inner rollback

How Senior Engineers Fix It

Core corrective strategies:

  1. Refactor to flat transactions: Initialize parallel transactions via distinct DataSource instances
  2. Explicit savepoint control:
    await tx1.queryRunner.createSavepoint()
    try {
      /* inner work */
    } catch {
      await tx1.queryRunner.rollbackToSavepoint()
    }
  3. Boundary hardening:
    • Inject entity managers instead of transaction contexts
    • Verify database driver capabilities during startup
  4. Monitor transaction depth: Fail operations exceeding maxNestedTransactions threshold
  5. Compensating patterns: Implement saga transactions for distributed rollbacks

Why Juniors Miss It

Common oversight vectors:

  • Mistaking TypeORM API unification for database-engine consistency
  • Equating syntactic nesting with transactional isolation boundaries
  • Lack of visibility into underlying driver mechanisms (QueryRunner internals)
  • Insufficient testing of rollback edge cases across database types
  • Over-reliance on simplified development databases (e.g., SQLite in-memory behavior)