npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

adonisjs-transaction-decorator

v1.0.9

Published

Transaction decorator and mixin for AdonisJS applications

Readme

adonisjs-transaction-decorator

🚀 Advanced transaction management for AdonisJS applications

Powerful transaction decorator and mixin that provides automatic transaction handling with AsyncLocalStorage, retry mechanisms, debug logging, and comprehensive utilities.

Features

@transaction() decorator - Automatic transaction wrapping for methods
🔄 Nested transaction support - Reuses existing transactions
🔁 Retry mechanism - Configurable retry logic for failed transactions
📊 Debug logging - Transaction lifecycle logging with timing
Transactional mixin - Automatic transaction injection for Lucid models
🛠 Advanced utilities - Sequence, parallel, conditional execution
🔍 Transaction stats - Runtime statistics and monitoring
🎯 TypeScript first - Full type safety and IntelliSense support

Installation

npm install adonisjs-transaction-decorator

Quick Start

1. Transaction Decorator

import { transaction } from 'adonisjs-transaction-decorator'

class UserService {
  @transaction()
  async createUser(userData: any) {
    // This method automatically runs in a transaction
    const user = await User.create(userData)
    await Profile.create({ userId: user.id, ...profileData })
    return user
  }

  @transaction({ 
    debug: true, 
    retry: { attempts: 3, delay: 100 } 
  })
  async createUserWithRetry(userData: any) {
    // Automatic retry on failure with debug logging
    return await User.create(userData)
  }
}

2. Transactional Mixin

import { Transactional } from 'adonisjs-transaction-decorator'
import { BaseModel } from '@adonisjs/lucid/orm'

class User extends Transactional(BaseModel) {
  // All operations automatically use current transaction context
}

class UserController {
  @transaction()
  async store({ request }: HttpContext) {
    // All User operations will use the same transaction
    const user = await User.create(request.body())
    await User.query().where('active', false).update({ active: true })
    return user
  }
}

3. Programmatic Usage

import { runInTransaction } from 'adonisjs-transaction-decorator'

// Execute multiple operations in a transaction
const result = await runInTransaction(async () => {
  const user = await User.create({ name: 'John' })
  const profile = await Profile.create({ userId: user.id })
  return { user, profile }
}, { 
  debug: true,
  isolationLevel: 'repeatable read'
})

API Reference

Transaction Options

interface TransactionOptions {
  connection?: string              // Custom database connection
  isolationLevel?: IsolationLevel  // Transaction isolation level
  debug?: boolean                  // Enable debug logging
  timeout?: number                 // Transaction timeout (ms)
  retry?: {                        // Retry configuration
    attempts: number
    delay: number
  }
}

Core Functions

@transaction(options?: TransactionOptions)

Decorator that wraps methods in a transaction.

class OrderService {
  @transaction({ isolationLevel: 'serializable' })
  async processOrder(orderData: any) {
    // High isolation level for critical operations
  }

  @transaction({ 
    retry: { attempts: 3, delay: 200 },
    debug: true 
  })
  async processPayment(paymentData: any) {
    // Retry failed payments with logging
  }
}

runInTransaction<T>(fn, options?): Promise<T>

Execute a function within a transaction programmatically.

const order = await runInTransaction(async () => {
  const order = await Order.create(orderData)
  await OrderItem.createMany(items.map(item => ({ ...item, orderId: order.id })))
  await Inventory.decrement(items)
  return order
}, { debug: true })

getCurrentTransaction(): TransactionClientContract | undefined

Get the current transaction client.

const trx = getCurrentTransaction()
if (trx) {
  // Manual database operations with current transaction
  await trx.raw('UPDATE counters SET value = value + 1')
}

isInTransaction(): boolean

Check if currently executing within a transaction.

if (isInTransaction()) {
  console.log('Running in transaction context')
}

getTransactionStats()

Get current transaction statistics.

const stats = getTransactionStats()
console.log(`Transaction ${stats.id} running for ${stats.duration}ms`)

Transactional Mixin

The Transactional mixin automatically injects the current transaction into all Lucid model operations:

class User extends Transactional(BaseModel) {
  // All static and instance methods automatically use current transaction
}

// Usage
@transaction()
async createUserWithPosts() {
  const user = await User.create({ name: 'John' })        // Uses transaction
  const post = await Post.create({ userId: user.id })     // Uses transaction
  await user.save()                                       // Uses transaction
  return user
}

Transaction Utilities

TransactionUtils.sequence<T>(operations, options?): Promise<T[]>

Execute operations sequentially within a transaction.

const results = await TransactionUtils.sequence([
  () => User.create({ name: 'Alice' }),
  () => User.create({ name: 'Bob' }),
  () => User.create({ name: 'Charlie' })
], { debug: true })

TransactionUtils.parallel<T>(operations, options?): Promise<T[]>

Execute operations in parallel within a transaction.

const [user, profile, settings] = await TransactionUtils.parallel([
  () => User.create(userData),
  () => Profile.create(profileData),
  () => Settings.create(settingsData)
])

TransactionUtils.conditional<T>(condition, operation, options?)

Conditionally execute operation in transaction.

const user = await TransactionUtils.conditional(
  () => shouldCreateUser,
  () => User.create(userData),
  { debug: true }
)

TransactionUtils.withSavepoint<T>(name, operation)

Execute operation with savepoint support (database dependent).

await runInTransaction(async () => {
  await User.create(userData)
  
  await TransactionUtils.withSavepoint('checkpoint', async () => {
    // This can be rolled back to savepoint on error
    await Profile.create(profileData)
  })
})

Advanced Examples

Error Handling with Retry

class PaymentService {
  @transaction({ 
    retry: { attempts: 3, delay: 1000 },
    debug: true 
  })
  async processPayment(amount: number, cardToken: string) {
    // Automatically retries on failure
    const charge = await this.chargeCard(cardToken, amount)
    await Transaction.create({ amount, status: 'completed' })
    await this.sendReceipt(charge.receiptEmail)
    return charge
  }
}

Complex Business Logic

class OrderService {
  @transaction({ isolationLevel: 'serializable' })
  async fulfillOrder(orderId: number) {
    const order = await Order.findOrFail(orderId)
    
    // All operations use the same transaction
    for (const item of order.items) {
      await Inventory.decrement(item.productId, item.quantity)
      await ShippingLabel.create({ orderId, itemId: item.id })
    }
    
    await order.merge({ status: 'fulfilled' }).save()
    await this.notifyCustomer(order.customerId)
    
    return order
  }
}

Multiple Database Connections

class DataMigrationService {
  @transaction({ connection: 'analytics', debug: true })
  async migrateUserData(userId: number) {
    // Uses 'analytics' database connection
    const user = await User.create(userData)
    await UserAnalytics.create({ userId: user.id })
    return user
  }
}

Monitoring and Debugging

class ReportService {
  @transaction({ debug: true })
  async generateReport() {
    const stats = getTransactionStats()
    console.log(`Report generation started: ${stats.id}`)
    
    // Complex report generation...
    await this.aggregateData()
    await this.generateCharts()
    await this.saveReport()
    
    const finalStats = getTransactionStats()
    console.log(`Report completed in ${finalStats.duration}ms`)
  }
}

Best Practices

1. Use Appropriate Isolation Levels

// For critical operations requiring consistency
@transaction({ isolationLevel: 'serializable' })
async transferMoney(fromAccount: number, toAccount: number, amount: number) {
  // Prevents phantom reads and ensures data consistency
}

// For most operations (default)
@transaction({ isolationLevel: 'read committed' })
async updateProfile(userId: number, data: any) {
  // Good balance of consistency and performance
}

2. Enable Debug Logging in Development

// In development
@transaction({ debug: true })

// In production
@transaction({ debug: false })

3. Use Retry for Network-Dependent Operations

@transaction({ 
  retry: { attempts: 3, delay: 500 } 
})
async syncWithExternalAPI() {
  // Handles temporary network issues
}

4. Combine with Lucid Model Events

class User extends Transactional(BaseModel) {
  @beforeCreate()
  static async hashPassword(user: User) {
    // This will use the current transaction context
    user.password = await Hash.make(user.password)
  }
}

Compatibility

  • AdonisJS: ^6.0.0
  • Node.js: >=18.0.0
  • TypeScript: ^5.0.0
  • Databases: PostgreSQL, MySQL, SQLite, MSSQL (via Lucid ORM)

Contributing

Contributions are welcome! Please read our Contributing Guide for details.

License

MIT License - see LICENSE file for details.

Support