Skip to content

Architecture

Pavel Erokhin edited this page Oct 19, 2026 · 1 revision

WorldAPI is a Kotlin plugin for Paper. A Ktor server answers HTTP and the WebSocket next to the game, and everything that touches Bukkit runs on the server thread.

The parts

Path What it does
WorldApiPlugin.kt, core/ Startup and shutdown; MainThread.kt runs Bukkit calls on the server thread, with a timeout
config/ config.yml through kaml in strict mode; an unknown key is an error, not a silent default
api/ The Ktor server, the endpoint DSL, auth, permissions and groups (Scope.kt), rate limits, trusted proxies, the HTTPS warning (Exposure.kt), and the OpenAPI generator
routes/ One file per area; each endpoint declares its permission, so the router and docs/openapi.json come from the same list
files/ /files: which paths may be read or written (ServerFiles), paged and grep reads, writes with backups, and key edits in YAML, .properties, and JSON
events/ Every topic and its privacy (Topic.kt), listeners, the sampler for state topics, snapshots, and Visibility, which strips what visitors mustn't see
ws/ The WebSocket hub: per-client outboxes; state topics coalesce, event topics drop the oldest
stats/ Statistics from live players and the stats files, leaderboards, aggregates
store/ Event history, sessions, audit log, experience: Exposed over HikariCP on SQLite, PostgreSQL, or MySQL/MariaDB. Writes queue on one thread; reads wait for the writes queued before them
extensions/ The bridge other plugins call (see Extensions)
plan/ The section on Plan's pages; its classes load only when Plan is enabled
tokens/, command/ Issued tokens (stored as SHA-256), the in-game menus, /worldapi
script/ Java scripts: compiled by the bundled ECJ, loaded by a filtered class loader, checked against blocked calls
admin/ The admin panel: a React app built with Bun into the jar, served at /admin, with its sign-in and accounts in admin/*.kt

Rules that keep it working

  • Bukkit calls happen on the server thread through MainThread; IO, the database, and the network never do. A request that waits too long for the thread gets 503 before it touches the world.
  • Visitors never get coordinates, inventories, addresses, or chat. A new topic or field declares its privacy, and Visibility strips it.
  • Vanished players look offline to every client without players:moderate.
  • One endpoint list feeds both the router and the OpenAPI document, and a test fails when the committed docs/openapi.json falls behind.
  • The extension bridge is a contract other plugins compile against without WorldAPI's classes: plain Java types in, maps out, and a version on every request.

Why the build looks this way

  • paper-plugin.yml, because WorldAPI is Paper-only and gets an isolated class loader and Brigadier commands.
  • Dependencies are shaded and relocated under com.mairwunnx.worldapi.libs, so the jar works offline and can't clash with FAWE, BlueMap, Plan, or TAB.
  • ECJ stays unrelocated, because it finds its message bundles by package name.
  • The SQLite driver comes from Paper; the PostgreSQL and MariaDB drivers are named through class references, so the relocation holds.

Next: Development.

Clone this wiki locally