Ledger

Player data as a ledger, not a document. You never write state. You write down the change you want, a function you own decides if it's allowed, and state is what falls out of replaying those changes.

local Store = Ledger.New({
	Name = "PlayerData",
	Default = { Gold = 100 },
	Reducer = function(State, Op)
		if Op.Kind == "Earn" then
			if type(Op.Amount) ~= "number" or Op.Amount <= 0 then
				return nil
			end
			return { Gold = State.Gold + Op.Amount }
		end

		if Op.Kind == "SpendGold" then
			if type(Op.Amount) ~= "number" or Op.Amount > State.Gold then
				return nil -- refused, on every server, forever
			end
			return { Gold = State.Gold - Op.Amount }
		end

		return nil -- an op this build has never heard of
	end,
})

Store:Load(Player)
Store:Expect(Player):Apply("SpendGold", { Amount = 25 })

No session locks

Two servers can write the same key at once. The fold decides who wins and every server gets the same answer. Nothing to lease, nothing to wait out after a crash.

A reducer you own

State is a pure fold over a log of ops. Your reducer decides if each one is allowed, so the bad state is unreachable instead of being caught after the fact.

Money that moves properly

Transfers move a balance through an escrow, deduped by id, and they fix themselves after a crash. Transactions commit two to four keys, all or nothing.

Once, forever

Name an op after a receipt or an order and it applies one time on that key, across compaction, rejoins, and two servers racing the same replay.