This example uses async operations with callbacks for non-blocking inventory management.
local inventoryDB = db:Collection("inventories")
-- Create indexes (sync is fine for initialization)
inventoryDB:CreateIndex({ steamid = 1 }, true, "steamid_unique")
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
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
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
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
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
-- 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
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:
This pattern keeps the server responsive while ensuring data consistency!