Developer
Persistence
Repositories, the connect-only MySQL policy, and schema verification.
Policy
ModularJobs supports MySQL 8 only and never runs DDL from a runtime process — neither the Paper plugin nor the Rust REST API creates tables.
- Source of truth:
modularjobs-paper/src/main/resources/sql/mysql.sql - Apply:
./scripts/apply-mysql-schema.sh - Verification:
SchemaPresencechecks required tables and migration-sensitive columns on connect. Missing tables orcurrent_node_keycolumns fail enable; runtime never repairs the schema.
Lab/integration tests may apply SQL explicitly; production paths stay connect-only. Do not reintroduce boot-time DDL or other dialects.
Repositories
| Repository | Backing table(s) | Notes |
|---|---|---|
| Configured job trees | (config-backed) | Flat jobs.yml nodes grouped into complete root-owned Job trees |
PlayerJobStateRepository | job_progression, archive_job_progression | Root tree key, active node key, tree-wide XP; archive on leave |
RelationalJobTaskRepositoryImpl | job_tasks, job_task_payables, payable_records | Direct node task definitions applied through editor paths |
TimedBoostRepository | time_boosts, time_boost_identity | Timed boost persistence |
PlayerUpgradeRepository | player_upgrades | Skill points, unlocked nodes, node levels |
SQL lives in classpath resources under
modularjobs-paper/src/main/resources/sql/<table>/ (.sql per operation) —
repositories do not inline their operation queries.
Schema notes
- Money/XP are
DECIMAL(38,10); key-bearing identifiers use boundedVARCHAR(191). job_task_payables.job_task_id→job_tasks.task_idON DELETE CASCADE.editor_sessionsstores the payload asJSONwithexpires_atandupdated_at; the REST API is the session authority.player_upgradeskeepstotal_skill_points,unlocked_nodes(legacy migration), and authoritativenode_levels.job_progression.job_keyis the root tree key;current_node_keyis the active root/specialization node. XP is tree-wide.
Connection pooling
HikariCP pools are keyed by jdbc-url + username — identical sections share
one pool. database.yml sections: payable, timed-boost, progression,
upgrades.
Job and task ownership
jobs.ymlstays flat. Root sections own max level, leveling curve, and payout curves; child sections declareparentand inherit those tree-wide rules.- A task row's
job_keyidentifies its owning node. At runtime, tasks resolve root-to-active-node; a descendant replaces the whole task with the same action/context key while unrelated ancestor tasks remain. - The editor exports direct definitions per node. It does not materialize inherited tasks into child rows.
- The plugin does not seed task rows. An empty MySQL task table remains empty until the editor/repository path or reviewed SQL writes definitions.
- The
fisher→fishermanrename still needs an operator-managed data update.
Concurrency invariants
- Multi-XP awards in one payment reload player job state per payable so awards accumulate.
- Write-back flush failure retains pending writes — no older-XP clobber.
- Kill multi-damage pays each qualifying contributor above the cutoff.
Test strategies
- Pure unit tests for curves/mappers — no DB.
- MockBukkit 26.2 for listener/payment/Bukkit-touching tests.
- Repository SQL tests need a live MySQL
(
MODULARJOBS_TEST_MYSQL_*or defaultlocalhost:13306); they skip when unavailable.