Welcome to the Jose Madrid Salsa developer docs — explore features, APIs, and deployment guides.
Jose Madrid SalsaJMS Docs

Inventory Management

Inventory tracking with reservations, alerts, transaction history, serializable transactions, and email notifications

Inventory Management

The inventory system tracks stock levels, handles reservations during checkout, creates transaction audit trails, and sends email alerts for low-stock and out-of-stock conditions. All critical operations use Serializable transaction isolation to prevent race conditions.

Architecture

inventory-manager.ts

Stock Status

Products have a computed stockStatus field:

StatusCondition
IN_STOCKAvailable > lowStockThreshold
LOW_STOCK0 < Available ≤ lowStockThreshold
OUT_OF_STOCKAvailable ≤ 0

Where Available = inventory - stockReserved.

Core Operations

Adjust Inventory

adjustInventory() changes a product's stock level and creates a transaction record:

interface InventoryAdjustment {
  productId: string
  quantity: number    // Positive to add, negative to remove
  type: InventoryTransactionType
  reason?: string
  notes?: string
  orderId?: string
  userId?: string
}

The function validates that:

  • New stock is not negative
  • New stock does not drop below reserved stock

Reserve Inventory

reserveInventory() holds stock during checkout using Serializable transaction isolation:

export async function reserveInventory(reservation: InventoryReservation) {
  return withSerializableRetry(() =>
    prisma.$transaction(
      async (tx) => {
        // Validate available stock
        // Increment stockReserved
        // Create RESERVATION transaction
      },
      { isolationLevel: 'Serializable' }
    )
  )
}

Reserve Multiple Products

reserveMultipleProducts() atomically reserves stock for all items in a cart within a single Serializable transaction. If any product has insufficient stock, the entire reservation rolls back:

Validate All

Check every product has sufficient available stock.

Reserve All

Only if all validations pass, reserve inventory for every product atomically.

Release Inventory

releaseInventory() reverses a reservation (e.g., order cancellation) by decrementing stockReserved.

Deduct Reserved Inventory

deductReservedInventory() is called when an order is completed. It decreases both stockReserved and actual inventory, creating a SALE transaction record.

Serialization Conflict Retry

All reservation operations retry up to 3 times on Prisma error code P2034 (serialization conflict):

async function withSerializableRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn()
    } catch (error: any) {
      if (error?.code === 'P2034' && attempt < maxRetries - 1) continue
      throw error
    }
  }
}

Inventory Alerts

The checkAndUpdateAlerts() function runs after every stock change:

ConditionAction
Stock normal (> threshold)Resolve all active alerts
Out of stock (= 0)Create OUT_OF_STOCK alert if none exists
Low stock (0 < stock ≤ threshold)Create LOW_STOCK alert if none exists

Email Notifications

New alerts trigger email notifications to addresses in INVENTORY_ALERT_EMAILS:

const adminEmails = process.env.INVENTORY_ALERT_EMAILS?.split(',') || []

Emails include product name, SKU, current stock level, and a link to the product in the admin panel.

Transaction History

Every stock change creates an InventoryTransaction record with:

  • Transaction type (SALE, RESERVATION, RELEASE, ADJUSTMENT, etc.)
  • Previous and new stock levels
  • Reason and notes
  • Associated order ID and user ID

The getInventoryHistory() function retrieves the last 50 transactions for a product.

Alert syncing is non-critical -- if it fails after a successful stock change, the error is logged but the stock adjustment is not rolled back. The stock change has already been committed.

How is this guide?

Edit on GitHub

Last updated on

On this page