@motioneffector/inventory

Documentation

Transactions API

Atomic operation wrapper for grouped inventory changes.


transaction()

Wraps multiple operations in an atomic unit. If any operation throws, all changes roll back.

Signature:

function transaction(fn: () => void): void

Parameters:

Name Type Required Description
fn () => void Yes Function containing inventory operations

Returns: void

Example:

import { createInventoryManager } from '@motioneffector/inventory'

const inventory = createInventoryManager()
inventory.createContainer('player', { mode: 'unlimited' })
inventory.createContainer('shop', { mode: 'unlimited' })
inventory.addItem('player', 'gold', 100)
inventory.addItem('shop', 'sword', 1)

// Successful transaction
inventory.transaction(() => {
  inventory.removeItem('player', 'gold', 50)
  inventory.addItem('player', 'sword', 1)
  inventory.removeItem('shop', 'sword', 1)
  inventory.addItem('shop', 'gold', 50)
})

// Both containers updated
console.log(inventory.getQuantity('player', 'gold'))  // 50
console.log(inventory.getQuantity('player', 'sword')) // 1

Rollback on Error

Any error inside the transaction triggers a complete rollback.

import { createInventoryManager } from '@motioneffector/inventory'

const inventory = createInventoryManager()
inventory.createContainer('bag', { mode: 'unlimited' })
inventory.addItem('bag', 'gem', 10)

try {
  inventory.transaction(() => {
    inventory.removeItem('bag', 'gem', 5)
    // Simulate failure
    throw new Error('Something went wrong')
  })
} catch (e) {
  console.log('Transaction failed:', e.message)
}

// Rollback occurred - still have 10 gems
console.log(inventory.getQuantity('bag', 'gem')) // 10

Validation Pattern

Check operation results and throw to trigger rollback.

import { createInventoryManager } from '@motioneffector/inventory'

const inventory = createInventoryManager()
inventory.createContainer('player', { mode: 'weight', maxWeight: 20 })
inventory.createContainer('shop', { mode: 'unlimited' })
inventory.addItem('player', 'gold', 100)
inventory.addItem('shop', 'heavy-armor', 1)

function buyItem(itemId: string, price: number): boolean {
  try {
    inventory.transaction(() => {
      // Check gold
      const paid = inventory.removeItem('player', 'gold', price)
      if (paid < price) {
        throw new Error(`Need ${price} gold, only have ${paid}`)
      }

      // Check item fits
      const result = inventory.addItem('player', itemId, 1)
      if (!result.success) {
        throw new Error(`Cannot carry ${itemId}: ${result.reason}`)
      }

      // Remove from shop
      inventory.removeItem('shop', itemId, 1)
    })
    return true
  } catch (e) {
    console.log('Purchase failed:', e.message)
    return false
  }
}

buyItem('heavy-armor', 50) // May fail if too heavy

Important Notes

Synchronous Only

Transactions are synchronous. Async operations inside the callback will not be rolled back.

// WRONG - async won't roll back properly
inventory.transaction(async () => {
  await someAsyncOperation()
  inventory.addItem('bag', 'item', 1)
})

// RIGHT - prepare data first
const data = await someAsyncOperation()
inventory.transaction(() => {
  inventory.addItem('bag', data.itemId, 1)
})

No Nested Transactions

The library doesn't support nested transactions. An inner transaction() call is part of the outer one.

inventory.transaction(() => {
  inventory.addItem('bag', 'item1', 1)

  // This is NOT a separate transaction
  inventory.transaction(() => {
    inventory.addItem('bag', 'item2', 1)
    throw new Error('fail')
  })
  // Outer transaction also fails - both items rolled back
})

Events Still Fire

Events fire during the transaction even if it later rolls back. Design your event handlers accordingly.

let log: string[] = []

inventory.on('itemAdded', (e) => {
  log.push(`added ${e.itemId}`)
})

try {
  inventory.transaction(() => {
    inventory.addItem('bag', 'item', 1) // Event fires here
    throw new Error('rollback')
  })
} catch {}

console.log(log) // ['added item'] - event fired even though rolled back