22. September 2026 · Offensichtlicher
Transaktionen im GUI anzeigen
Reason to
ItemStack
Every economical transaction writes one row. The row answers who, how much, when — and, in one free-form string, why. Turning that string back into something a player can look at is the whole job. Here is the machine that does it.
transactions
ALL ACCOUNTS| user | v | Δ | reason |
|---|
rendering
JVMplayer view
GENERIC_54One row per transaction, versioned per user
version counts up per user, so the pair (user, version) is unique without a sequence, and gaps are impossible: row v84 is that account's 84th movement, full stop. delta is relative — never a balance — so replaying the table from zero reconstructs every account, and a lost write shows up as a hole in the numbering.
The caller — a shop plugin, a plot manager, an admin command — supplies the reason. The economy never invents one. That single decision is the abstraction: the ledger stays plugin-agnostic, and each plugin keeps its own syntax, as long as that syntax carries enough context to trace the row back to the event that caused it.
The ledger is server-wide. The GUI is one player's.
Without the predicate, the first 54 rows of a busy server are four accounts' worth of noise and nobody's history. The filter is not a convenience on top of the view — it is what makes the view mean anything. Swap the account in the query bar above and the same 84-row ledger produces four completely different chests.
Where it sits matters. Filtering in SQL is one index scan on (uuid, created_at); filtering after the fetch means pulling the whole table across to throw most of it away. The LIMIT then has to match the container — 54 slots, 54 rows — or the last page silently disappears.
What it does not touch: the renderer. create(reason) never sees a uuid, a timestamp or a delta — only the string. Which account you scope to changes which reasons arrive, never how one is resolved. That separation is why the filter could be added here, late, without opening a single renderer.
First token names the source. Everything after it is context.
Nothing validates these strings. Streetshop packs its context into colon-separated segments; the admin command uses plain words; plot-merge appends chunk coordinates in brackets it made up itself. Three plugins, three grammars, one column.
Which is exactly why rendering can't be a switch. The renderer that understands streetshop: is the same code that wrote it — it lives in that plugin and registers itself. The economy only knows how to ask.
And when no one claims a string — rewards:, pay to, and the BUY/SELL rows all come from plugins that never registered a renderer — the chain runs out and the fallback returns a question mark. An unknown reason is still a rendered reason, and the GUI never has a hole in it.
Two types, one linked list, no registry
GLOBAL is a head node that supports nothing and delegates everything. Adding a plugin means one add() call at startup — no enum to extend, no switch to find, no central list of known sources. The economy module never learns that street shops exist.
cache keys the finished Render by reason string — not by row, not by account. Scope the query bar to e2539420-… and watch the ADMIN_CMD ADD VollmilchZiege burst resolve once and replay free for sixteen more rows: the chain walk is skipped, the same ItemStack is handed back. The cache outlives the filter, because the filter was never part of the key.
All 84 rows, both queries merged
The server-wide ledger. Rows outside the current filter are dimmed; the icon column is not stored anywhere at all — it is what the chain produced.
| uuid | version | relative_change | reason | created_at |
|---|