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.
Mental model
Section titled “Mental model”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 ontoData— it never removes or mutates the stored entry. Roll back to the same id, mutateDataagain, 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.Datain memory — if you want the rolled-back state to survive a restart, callProfile: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 failurelocal 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 backendendAPI reference
Section titled “API reference”| 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. |
Gotchas
Section titled “Gotchas”- 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.