Examples

Inventory System

Item management and storage with async operations

This example uses async operations with callbacks for non-blocking inventory management.

Setup

local inventoryDB = db:Collection("inventories")

-- Create indexes (sync is fine for initialization)
inventoryDB:CreateIndex({ steamid = 1 }, true, "steamid_unique")

Initialize Inventory

function InitInventory(steamid, callback)
    inventoryDB:InsertOneAsync({
        steamid = steamid,
        max_slots = 20,
        items = {},
        created_at = os.time()
    }, function(err, id)
        if err then
            ErrorNoHalt("Failed to create inventory: " .. err .. "\n")
            if callback then callback(false) end
            return
        end
        
        print("✓ Created inventory for " .. steamid)
        if callback then callback(true, id) end
    end)
end

Add Item

function AddItem(steamid, itemId, quantity, callback)
    -- First check if item already exists
    inventoryDB:FindOneAsync({
        steamid = steamid,
        ["items.item_id"] = itemId
    }, function(err, has)
        if err then
            ErrorNoHalt("Failed to check inventory: " .. err .. "\n")
            if callback then callback(false, "Database error") end
            return
        end
        
        if has then
            -- Item exists - increment quantity
            inventoryDB:UpdateOneAsync(
                {
                    steamid = steamid,
                    ["items.item_id"] = itemId
                },
                {
                    ["$inc"] = { ["items.$.quantity"] = quantity }
                },
                false,
                function(err, count)
                    if err then
                        ErrorNoHalt("Failed to add item: " .. err .. "\n")
                        if callback then callback(false, "Update failed") end
                    elseif count > 0 then
                        print("✓ Added " .. quantity .. "x " .. itemId)
                        if callback then callback(true, "updated") end
                    end
                end
            )
        else
            -- New item - add to array
            inventoryDB:UpdateOneAsync(
                { steamid = steamid },
                {
                    ["$push"] = {
                        items = {
                            item_id = itemId,
                            quantity = quantity,
                            added_at = os.time()
                        }
                    }
                },
                false,
                function(err, count)
                    if err then
                        ErrorNoHalt("Failed to add item: " .. err .. "\n")
                        if callback then callback(false, "Insert failed") end
                    elseif count > 0 then
                        print("✓ Added new item " .. itemId .. " x" .. quantity)
                        if callback then callback(true, "added") end
                    end
                end
            )
        end
    end)
end

Remove Item

function RemoveItem(steamid, itemId, quantity, callback)
    -- First get the current inventory
    inventoryDB:FindOneAsync({ steamid = steamid }, function(err, inv)
        if err then
            ErrorNoHalt("Failed to get inventory: " .. err .. "\n")
            if callback then callback(false, "Database error") end
            return
        end
        
        if not inv then
            if callback then callback(false, "Inventory not found") end
            return
        end
        
        -- Find current quantity
        local currentQty = 0
        for _, item in ipairs(inv.items or {}) do
            if item.item_id == itemId then
                currentQty = item.quantity
                break
            end
        end
        
        if currentQty < quantity then
            if callback then callback(false, "Insufficient quantity") end
            return
        end
        
        if currentQty == quantity then
            -- Remove item completely
            inventoryDB:UpdateOneAsync(
                { steamid = steamid },
                { ["$pull"] = { items = { item_id = itemId } } },
                false,
                function(err, count)
                    if err then
                        ErrorNoHalt("Failed to remove item: " .. err .. "\n")
                        if callback then callback(false, "Remove failed") end
                    elseif count > 0 then
                        print("✓ Removed " .. itemId .. " from inventory")
                        if callback then callback(true, "removed") end
                    end
                end
            )
        else
            -- Decrease quantity
            inventoryDB:UpdateOneAsync(
                {
                    steamid = steamid,
                    ["items.item_id"] = itemId
                },
                { ["$inc"] = { ["items.$.quantity"] = -quantity } },
                false,
                function(err, count)
                    if err then
                        ErrorNoHalt("Failed to update quantity: " .. err .. "\n")
                        if callback then callback(false, "Update failed") end
                    elseif count > 0 then
                        print("✓ Removed " .. quantity .. "x " .. itemId)
                        if callback then callback(true, "decreased") end
                    end
                end
            )
        end
    end)
end

Get Inventory

function GetInventory(steamid, callback)
    inventoryDB:FindOneAsync({ steamid = steamid }, function(err, inv)
        if err then
            ErrorNoHalt("Failed to get inventory: " .. err .. "\n")
            callback(nil, err)
            return
        end
        
        callback(inv)
    end)
end

Check Item Count

function HasItem(steamid, itemId, minQuantity, callback)
    GetInventory(steamid, function(inv, err)
        if err or not inv then
            callback(false)
            return
        end
        
        for _, item in ipairs(inv.items or {}) do
            if item.item_id == itemId then
                callback(item.quantity >= minQuantity, item.quantity)
                return
            end
        end
        
        callback(false, 0)
    end)
end

Example: Purchase Item

-- Complete example with async operations
function PurchaseItem(ply, itemId, quantity, cost)
    local steamid = ply:SteamID()
    
    -- Step 1: Check if player has enough credits
    playersDB:FindOneAsync({ steamid = steamid }, function(err, playerData)
        if err or not playerData then
            ply:ChatPrint("Error loading player data!")
            return
        end
        
        local credits = playerData.credits or 0
        if credits < cost then
            ply:ChatPrint("Insufficient credits! Need " .. cost .. ", have " .. credits)
            return
        end
        
        -- Step 2: Deduct credits
        playersDB:UpdateOneAsync(
            { steamid = steamid },
            { ["$inc"] = { credits = -cost } },
            false,
            function(err, count)
                if err or count == 0 then
                    ply:ChatPrint("Failed to deduct credits!")
                    return
                end
                
                -- Step 3: Add item to inventory
                AddItem(steamid, itemId, quantity, function(success, msg)
                    if success then
                        ply:ChatPrint("Successfully purchased " .. quantity .. "x " .. itemId .. "!")
                        ply:SetNWInt("Credits", credits - cost)
                    else
                        -- Refund credits on failure
                        playersDB:UpdateOneAsync(
                            { steamid = steamid },
                            { ["$inc"] = { credits = cost } }
                        )
                        ply:ChatPrint("Purchase failed: " .. msg)
                    end
                end)
            end
        )
    end)
end

Display Inventory Command

concommand.Add("inventory", function(ply)
    if not IsValid(ply) then return end
    
    GetInventory(ply:SteamID(), function(inv, err)
        if err then
            ply:ChatPrint("Error loading inventory!")
            return
        end
        
        if not inv or not inv.items or #inv.items == 0 then
            ply:ChatPrint("Your inventory is empty!")
            return
        end
        
        ply:ChatPrint("=== Your Inventory ===")
        ply:ChatPrint("Slots: " .. #inv.items .. "/" .. inv.max_slots)
        
        for i, item in ipairs(inv.items) do
            ply:ChatPrint(string.format("%d. %s x%d", i, item.item_id, item.quantity))
        end
    end)
end)

Notice how the purchase example chains multiple async operations:

  1. Check player credits
  2. Deduct credits
  3. Add item to inventory
  4. Refund on failure

This pattern keeps the server responsive while ensuring data consistency!