Ledger
Player data for Roblox that can't be duplicated or lost halfway. You describe each change, your own function decides if it's allowed, and Ledger saves it exactly once.
type PlayerData = { Gold: number, Items: { string } }
type PlayerOps = {
AddGold: { Amount: number },
SpendGold: { Amount: number },
BuyItem: { Item: string, Price: number },
}
local PlayerStore = Ledger.New<<PlayerData, PlayerOps>>({
Name = "PlayerData",
Keys = "Player",
Default = { Gold = 0, Items = {} },
MustExist = false,
Erasable = true,
Balances = {
Gold = { Credit = "AddGold", Debit = "SpendGold" },
},
Reducer = function(Data, Op)
if Op.Kind == "BuyItem" then
if Data.Gold < Op.Price or table.find(Data.Items, Op.Item) then
return nil
end
local Items = table.clone(Data.Items)
table.insert(Items, Op.Item)
local New = table.clone(Data)
New.Gold -= Op.Price
New.Items = Items
return New
end
return nil
end,
})
local Session = PlayerStore:Load(Player)
if Session then
local Ok, Why = Session:Apply({ Kind = "BuyItem", Item = "Sword", Price = 50 })
if not Ok then
warn("Couldn't buy the sword:", Why)
end
endEvery change saved once
A change is never applied twice, even when a server crashes mid-save or a DataStore outage makes Ledger send it again.
Your rules decide
Your reducer says whether each change is allowed. If a player can't afford something, it's refused on every server, every time.
Trades that can't half happen
Ledger.Tx changes several players or keys at once: all of them or none. It works across servers, and for players who are offline.
Answers you can act on
Every call answers true, or one of 15 reasons. Each reason means one thing and has one thing to do about it.
Limited items and counters
Sell a fixed stock from every server at once without overselling, and keep totals the whole game adds to.
Updates that keep old saves
Change the shape of your data with migrations. Old servers and new ones can run side by side during an update.