Ledger

Store

Every call on the object Ledger.New returns.

Look up any call on a store.

local Ok, Why, Info = Bank:Edit(Key, { Kind = "AddGold", Amount = Amount })
if not Ok then
	warn("add failed:", Why)
end

local State, Why = Bank:Peek(Key, 30) -- up to 30 s old is fine

A store holds one name and every key under it. A key is a string, or a Player or UserId in a player store. Most calls work on any store. The player calls (Load and friends) work on player stores only, and throw on a string store.

A bad call throws before anything is sent: a bad key, a wrong option name, or a call made from inside a reducer. Answers are never thrown. See Answers.

An op kind that isn't in your Ops type doesn't throw. Your reducer gets it, returns nil, and the call answers Refused. With --!strict, Studio's type check catches it before you run the game.

Changing data

CallWhat it does
Edit(Key, Op, Options?)One change, saved before it answers.
Reset(Key, Options?)Replaces a key's data with the Default or a state you give.
Erase(Key, Options?)Removes a key. Erasable stores only.
Bump(Total, Amount, Options?)Adds to a total.
Leg(Key, Op, Options?)Builds one entry for Ledger.Tx. Sends nothing.

Edit

Options is { MustExist?, Id?, IdAt? }. MustExist overrides the store's setting for this call. A string Id needs IdAt too. Edit is safe on a key a player session holds.

AnswerMeansDo
trueSaved.Done.
Refused or FullYour reducer said no, or the data would pass the size cap. Info.State is the data it judged.Final for this name. Do not resend.
MissingMustExist and the key is not there.Create it first, or turn MustExist off.
Spent, Expired, NoRoomThe name is used up, forgotten, or the key's name list is full.See Making a change happen once.
Busy or ClosedNothing was sent.Busy: send the same call again later. Closed: stop.
Behind or UnreadableA newer build owns the key, or the key cannot be read.Stop writing it from this build.
UnresolvedNot known yet. Info.Outcome is known later.A named Edit: send it again, same name. An unnamed one is still being sent: do not resend.

Two things you may see:

  • Busy right after you resend a named Edit. After Unresolved, Ledger keeps sending the first one itself, and it won't send the same name twice at once. Expect Busy for a moment, then send it again. The name makes sure it only counts once.
  • An Edit that waits. If a trade is still being finished on the key, the Edit waits for it. When the server running that trade has gone down, finishing it takes longer, and the Edit answers Unresolved after 30 seconds rather than Busy. Ledger keeps sending it after that.

An op kind your reducer doesn't handle is refused. Studio's type check catches a kind that isn't in Ops before you run the game.

Reset and Erase

local Name = Ledger.Id()
if Name then
	local Ok, Result = Bank:Erase(Key, { Id = Name })
end

Options for both is { Id } with a Ledger.Id() name, or nothing. A string Id throws, and so does IdAt beside it. Reset also takes State, a complete state in the newest shape. Make the name just before the call: a server answers Expired for its own name once it is 6 minutes old.

AnswerMeansDo
true, CutDone. Cut.Losses lists the balances that were erased, newest last.Log Cut.Losses now. After an Erase this is the only time you get it. After a Reset, Store:Losses keeps them for 7 days.
true, no LossesA repeat of the same name, already done. Also what Erase with a Ledger.Id() name answers for a key that was never saved.Done.
MissingNothing to reset or erase. Erase with no name answers this for a key that was never saved.Done: the key is gone.
ExpiredThe name is too old. Nothing was sent.Make a new name.
SpentThe name was used for something else.Make a new name.
Busy or ClosedNothing was sent.Send again with the same name.
UnresolvedNot known yet.Send again with the same name, soon.
Behind, Unreadable, FullA newer build owns the key, it cannot be read, or the given State is too big.See Answers.

An Erase on a store with Erasable = false throws. See Deleting data.

Bump

task.spawn(function() -- Bump can wait up to a minute
	Bank:Bump("GoldAdded", Amount)
end)

The total must be declared in Totals. Amount is a whole number and may be negative. Unnamed bumps are best. After Unresolved, an unnamed bump is still being sent, so do not send it again. See Global counters.

Reading data

CallWhat it does
Peek(Key, Freshness?)The key's data.
Inspect(Key)The stored record, with its migration version.
Total(Name, MaxAge?)A declared total.
Follow(Key)An observer of the key's data.
Stale()An observer of keys this server wrote that its view is behind on.
Keys(Prefix?)Pages of every key, or of the keys that start with Prefix.

Peek

Freshness is a number of seconds (0 to 2,592,000), or { Fresh = true }.

CallGives
Peek(Key)The saved data, read now.
Peek(Key, 0)The same: a plain Peek.
Peek(Key, 30)A recent copy that all servers share. Cheaper for a key many servers read often. See Reading data.
Peek(Key, { Fresh = true })The newest saved data. Costs a write, so use it rarely.
AnswerMeansDo
the dataThe key's data at some moment.Use it.
nil, MissingNever saved, or erased. It does not give the Default.Use your own default.
nil, BehindA newer build owns the key.Stop using this key. A plain Peek and { Fresh = true } answer this. Peek(Key, MaxAge) answers it too once the server has heard it, but a server that hasn't read the key since the update can answer the old copy once.
nil, UnresolvedThe read failed or took over 30 s.Show "loading", try later.
nil, Busy, Closed, UnreadableSee Answers.

nil never means empty. See Reading data.

Total

Total(Name, MaxAge?) answers a number, or nil, Unresolved or nil, Behind. It's for totals. For a quantity, use Quantity(Name):Total(). A name that isn't in Totals or Quantities throws.

Keys

local Pages = Bank:Keys()
local Page, Why = Pages:Next()

Next answers a list of up to 50 keys. nil, nil means the last page was read. nil, Unresolved means the fetch failed; the next Next tries the same page. Pages come in no set order.

Give a Prefix to list only the keys that start with it, such as Guilds:Keys("guild:"). It's for string stores; a player store throws.

Support calls

CallWhat it does
DidApply(Key, Name, Options?)Did a named op take effect? Options is { IdAt?, Probe? }.
Losses(Key)The balances that a Reset or Erase erased, kept for 7 days.
Pending(Key)Trade work that is not finished on a key.
Resettle(Key)Finishes that work now. Takes no options.
Versions(Key)Pages of the versions Roblox kept of the key.
PeekVersion(Key, Version)The key's data as one of those versions saved it.

See Support tools. Pending answers Unresolved after 30 s on a hung read, as Peek does.

Versions

local Pages = Bank:Versions(Key)
local Page, Why = Pages:Next()

Next answers a list of up to 1,024 versions, oldest first, and the same nil answers as Keys.

FieldWhat it is
VersionRoblox's id for the version.
CreatedTimeWhen it was made, in milliseconds since 1970. Not a Ledger.Now() time.
IsDeletedtrue for the tombstone Roblox keeps when Ledger removes an erased key, a while after the Erase. Before that, the newest version is the erase's seal, which PeekVersion answers Missing.

Roblox keeps one version for each hour the key was written, for 30 days. Ledger's own saves count too, so it isn't a list of your ops.

PeekVersion

local State, Why = Bank:PeekVersion(Key, Version)

Reads one version: the key's data exactly as it was saved then. Migrations added since don't run on it, so it can be in an older shape than your Default. A trade still unfinished when the version was saved isn't in it. It never changes the key: to put old data back, send it as an op or a Reset.

AnswerMeans
the dataThe key's data as that version saved it.
nil, MissingNo such version, or the key was erased then.
nil, BehindA newer version of Ledger saved it.
nil, UnreadableLedger didn't write it.
nil, UnresolvedThe read failed. Try again.
nil, ClosedThe server is shutting down.

Quantities

Quantity(Name) returns the quantity object. It throws if the name is not in Quantities.

Players

CallWhat it does
Load(Player)Loads the player's session. Yields. Answers the session, or nil, Reason.
Unload(Player)Saves and ends the session. Does nothing if there is none.
Get(Player)The session, or nil.
Expect(Player)The session, or throws.
IsLoaded(Player)true if a session is loaded.
WaitForLoaded(Player)Waits for a load that has started.
Read(Player)The session's data, or nil.

Load answers nil, nil if the player left. Otherwise the reason is Unresolved, Behind, Busy or Unreadable. A player with no saved data is never a failure: Load starts them from Default, even with MustExist = true. Then OnLoadFailed runs, and the player is kicked unless Kick = false.

Destroy

Destroy() ends every session and observer on the store. The store takes no more calls, and its name cannot be opened again on this server. You will rarely need it.

Next: Session

On this page