Skip to content

Profile Snapshots

Profile snapshots are an in-memory, point-in-time deep copy of a DataDriver profile’s Data, taken with profile:snapshot(label?) and restorable with profile:rollback(id). The defining decision: it’s a lighter, single-profile tool rather than a coordinated transaction — take a snapshot immediately before a risky mutation, mutate Data directly, and roll back on abort or failure instead of hand-writing compensation logic for every mutation site.

This composes with rather than replaces Transactions: Transactions gives you a coordinated atomic commit/rollback across two profiles (a trade that must succeed or fail on both sides at once), with persisted replay ids so a retried commit can’t double-apply. Snapshots cover one profile, live only in memory, and cost nothing to take — reach for Transactions when two stores must move together, and for a snapshot when you just want a local undo point around a single profile’s mutation.

profile:snapshot(label?) walks and rebuilds every nested table in Profile.Data — not a reference, a real deep copy — so mutating Data after a snapshot can never reach into the stored copy. Three things worth being precise about:

  • The ring keeps only the newest 8 snapshots. Taking a 9th snapshot silently evicts the oldest (index 1 of the ring) — no error, no warning. Rolling back to an evicted id returns false, err.
  • Snapshots survive repeated rollbacks. rollback(id) reads the snapshot and deep-copies it back onto Data — it never removes or mutates the stored entry. Roll back to the same id, mutate Data again, and roll back to it a second time: it restores the exact same state both times.
  • Session-only, never persisted. Snapshots live in server memory for the current session only; they are never written to the backend and vanish on server shutdown. A rollback only changes Profile.Data in memory — if you want the rolled-back state to survive a restart, call Profile:save() afterward.

The use case is admin tooling and risky-mutation guards: take a snapshot immediately before a trade, a crafting consumption, or an admin grant, mutate Data directly, and roll back on abort or failure.

-- Admin grant tool: snapshot before a risky mutation, roll back on failure
local function grantWithGuard(Profile, ItemId: string, Count: number, ItemCost: number)
local Id = Profile:snapshot("before admin grant")
Profile.Data.Inventory[ItemId] = (Profile.Data.Inventory[ItemId] or 0) + Count
Profile.Data.Coins -= Count * ItemCost
if Profile.Data.Coins < 0 then
local Ok, Err = Profile:rollback(Id)
assert(Ok, Err) -- the snapshot is still in the ring; this cannot fail here
return false, "insufficient coins"
end
return Profile:save() -- persist the grant; rollback alone never touches the backend
end
Member Description
profile:snapshot(label?: string) → id: number Deep-copies Profile.Data into the ring and returns its numeric id. label is freeform, for your own bookkeeping.
profile:snapshots() → { { Id: number, Label: string?, TakenAt: number, Data: T } } Every snapshot currently in the ring, oldest first, including each one’s own stored deep copy.
profile:rollback(id: number) → (ok: boolean, err: string?) Replaces Profile.Data with a fresh deep copy of that snapshot’s data. false, "no snapshot {id}" if the id was never taken or has since been evicted by the ring.
  • Rollback alone never touches the backend — call Profile:save() afterward if the rolled-back state needs to survive a restart.
  • The ring silently evicts past 8 entries; don’t rely on a snapshot id staying valid indefinitely if the profile keeps taking new ones.