| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
DataRegistry is HauntedMC's shared read/write boundary for canonical player identity and DataRegistry-owned player metadata on Velocity and Paper.
It owns player creation, username updates, active identity state, connection metadata, language and nickname preferences, playtime summaries, population membership and counters, and name history. Feature plugins own their own tables and should reference players by the stable scalar playerId.
Configure both the shell JAVA_HOME and the IDE Maven runner/importer to Java 25. The build deliberately rejects Java 26 or newer until the bundled DataProvider/Hibernate stack is qualified for it.
Start the server once to generate plugins/DataRegistry/config.yml, then review the database, feature, privacy, playtime/population mapping, retention, service-registry, and platform sections.
Defaults and comments live in dataregistry-core/src/main/resources/config.yml, which is the single documented configuration template. On startup, DataRegistry adds settings that are missing from an existing config while preserving operator-provided values and comments. Before an automatic rewrite, the previous file is copied to config.yml.bak. Warning-only validation does not rewrite the file or rotate that backup.
Invalid settings warn and fall back to their defaults. Unknown settings are preserved, reported by path, and ignored so configuration typos are visible. DataRegistry also reports incompatible YAML structure and unknown fields inside playtime.server-gamemode-rules. The startup log lists the exact default paths added during an upgrade so operators can review newly introduced settings.
The population domain is enabled by default. It requires online-status, sessions, and session-visits, which provide the canonical presence and visit evidence used by Population. Population does not require playtime. It reuses the existing playtime.server-gamemode-rules, ignored-gamemode policy, and unknown-server resolution so the network has one server-to-logical-gamemode mapping rather than two competing mapping systems.
The Velocity command /dataregistry (alias /dr) requires dataregistry.admin.
The command deliberately does not live-reload feature flags, database settings, or other non-playtime configuration; restart Velocity for those changes. Commands for disabled domains fail fast instead of running an unavailable backend operation.
The Velocity administration command uses the shared hauntedmc-theme-palette artifact, which is included in the bundled plugin jar and does not add a runtime plugin dependency.
Depend only on dataregistry-api as provided (replace the version with the release you target):
<dependency>
<groupId>nl.hauntedmc.dataregistry</groupId>
<artifactId>dataregistry-api</artifactId>
<version>1.14.4</version>
<scope>provided</scope>
</dependency>Use DataRegistryApi#players() for player data:
DataRegistryApiProvider apiProvider = /* platform plugin instance */;
PlayerData players = apiProvider.getDataRegistry().players();
UUID uuid = player.getUniqueId(); // snapshot platform state before async continuations
players.whenReady(uuid).thenAccept(identity -> {
identity.ifPresent(value -> {
long playerId = value.playerId();
UUID canonicalUuid = value.uuid();
String username = value.username();
});
});DataRegistryApiProvider#getDataRegistry() returns DataRegistryApi, not the core runtime. Platform plugins implement that provider capability; consumers can depend on dataregistry-api alone. There is deliberately no public path from that type to an ORM context, entity, repository, lifecycle writer, or DataProvider handle.
DataRegistryApiProvider#getDataRegistryInstrumentation() separately exposes the optional vendor-neutral observation capability of the active runtime. It does not add telemetry dependencies to DataRegistry or to normal API consumers. See docs/OBSERVATION.md for attachment, completion, context-propagation, privacy/cardinality, and DataRegistry-versus-DataProvider instrumentation rules.
Feature maintainers migrating from an older DataRegistry API should follow DOWNSTREAM_MIGRATION.md. DataRegistry 1.14.0 intentionally makes the Population facade part of the required DataRegistryApi contract; custom API implementations and test fakes must implement it.
Use whenReady(uuid) in join paths. It completes when DataRegistry has finished the authoritative lifecycle initialization for that player, including creation or username update if needed.
Use lookup-only methods outside lifecycle paths:
PlayerIdentity is immutable and standalone. It is safe to pass between feature layers and does not expose Hibernate-managed state.
Use PlayerProfile when a feature needs a read snapshot of several DataRegistry-owned fields:
players.findProfileByIdentifier(input, 20).thenAccept(profileOpt -> profileOpt.ifPresent(profile -> {
PlayerIdentity identity = profile.identity();
Optional<String> nickname = profile.nickname();
List<PlayerNameHistoryEntry> names = profile.nameHistory();
}));Profiles may include language, nickname, connection, online, activity, playtime, and name-history data depending on enabled modules and available rows. Missing optional feature data is represented as Optional.empty() or an empty list. Profile projection is assembled by DataRegistry in one transaction for a consistent snapshot.
Use the specific facade methods when a full profile is unnecessary:
Public persistence reads and DataRegistry-owned preference writes return CompletionStage and run on DataRegistry's query executor with configured deadlines. Returned futures support cancellation when used as CompletableFuture. Development thread checks warn when likely event threads request queries or block pending query stages. Completion callbacks may run on DataRegistry worker or lifecycle threads, so snapshot Bukkit/Velocity state before starting async work and schedule platform API work back onto the platform thread when required.
Downstream plugins must not create, update, or merge canonical player rows. They may write only through the narrow DataRegistry methods for DataRegistry-owned preferences such as language and nickname.
DataRegistryApi#population() is the canonical population boundary for network-wide and logical-gamemode player counts. It is deliberately separate from playtime: playtime describes duration/activity, while Population describes membership, live presence, ordinal assignment, peaks, and population transitions.
A population scope is either the entire network or one normalized logical gamemode:
DataRegistryApi dataRegistry = apiProvider.getDataRegistry();
if (!dataRegistry.supports(DataRegistryFeature.POPULATION)) {
return;
}
PopulationData population = dataRegistry.population();
population.findNetworkSnapshot().thenAccept(snapshotOpt -> snapshotOpt.ifPresent(snapshot -> {
long uniquePlayers = snapshot.uniquePlayerCount();
long onlineNow = snapshot.currentOnline();
long allTimePeak = snapshot.onlinePeak();
}));
population.findSnapshot(PopulationScope.gamemode("survival"))
.thenAccept(snapshotOpt -> snapshotOpt.ifPresent(snapshot -> {
long localUniquePlayers = snapshot.uniquePlayerCount();
}));Population owns these canonical values:
Use membership reads when a feature needs the player's stable number:
population.findMembership(PlayerLookup.uuid(uuid), PopulationScope.gamemode("survival"))
.thenAccept(membershipOpt -> membershipOpt.ifPresent(membership -> {
long playerNumber = membership.ordinal();
}));Live ordinals are allocated atomically inside the same authoritative lifecycle transaction as status/session state. They are RECORDED_EXACT. When Population is introduced to a database that already contains DataRegistry history, existing network and gamemode memberships are reconstructed deterministically from the strongest canonical history available and are marked BACKFILLED_DETERMINISTIC instead of pretending those historic numbers were recorded live.
PopulationSnapshot.membershipBaselineQuality() and peakBaselineQuality() describe historical completeness. A new empty DataRegistry population starts VERIFIED. A database that already contains pre-Population history starts TRACKED_ONLY until an administrator explicitly verifies/seeds the historic baseline. Current/live state after Population starts is still maintained exactly.
For join-triggered features on Paper, use the durable join context instead of comparing timestamps or querying a count after the fact:
population.findJoinContext(player.getUniqueId(), serverName)
.thenAccept(contextOpt -> contextOpt.ifPresent(context -> {
if (context.gamemodeFirstJoinThisVisit()) {
long localNumber = context.gamemodeMembership().orElseThrow().ordinal();
}
if (context.networkFirstJoinThisSession()) {
long networkNumber = context.networkMembership().ordinal();
}
}));The context is valid only for the player's current durable online server/session/visit. This prevents a later query, reconnect, or backend switch from being mistaken for the original first join.
Downstream milestone-style consumers should poll the transition journal by cursor rather than repeatedly counting large player tables:
PopulationTransitionQuery query = PopulationTransitionQuery.after(lastProcessedId, 250)
.withCauses(Set.of(PopulationTransitionCause.LIVE));
population.findTransitions(query).thenAccept(batch -> {
if (batch.hasRetentionGapAfter(lastProcessedId)) {
// Consumer cursor is older than retained transition history: resnapshot/reconcile before continuing.
}
for (PopulationTransition transition : batch.transitions()) {
// MEMBERSHIP_ADDED, ONLINE_CHANGED, or ONLINE_PEAK_CHANGED
}
});Transition retention is configurable with retention.population-transition-days. Purging transition rows never removes memberships, ordinals, unique counts, current online state, or peak state. PopulationTransitionBatch includes the earliest/latest retained IDs so consumers can detect a cursor that fell behind retention.
Population reuses the existing canonical server-to-gamemode resolver. A transfer such as survival-1 to survival-2 therefore leaves the logical survival online count unchanged when both backend names map to survival; a transfer from survival to creative moves one player between those two logical scopes while network online remains unchanged.
DataRegistry owns the population facts, not feature policy. Reward commands, milestone thresholds, warning rules, welcome messages, and already-fired milestone claims belong in the consuming feature/plugin rather than in DataRegistry.
DataRegistry also exposes a process-local service catalog for feature-owned APIs. This lets enabled features share their own data and behavior without moving their tables into DataRegistry or forcing consumers to query another feature's ORM entities.
Feature plugins should publish narrow interfaces from their own lifecycle code:
FeatureServiceHandle handle = dataRegistry.featureServices().register(
"ServerFeatures",
"Vanish",
VanishAPI.class,
vanishService
);Consumers should resolve feature services by interface:
dataRegistry.featureServices()
.find(VanishAPI.class)
.ifPresent(vanish -> vanish.isVanished(playerId));Use find for optional integrations and require only when a feature cannot run without the dependency. Close the returned FeatureServiceHandle during feature disable, or use the ServerFeatures/ProxyFeatures lifecycle API manager, which publishes and unregisters services automatically.
The catalog is intentionally runtime-only. It does not provide cross-server RPC, cache persistence, or schema ownership. Exported interfaces should be stable, small, and expressed in scalar IDs or immutable value objects where possible.
Keep feature-owned data such as vanish, glow, nametags, friends, sanctions, client info, 2FA, voting, messaging, and logs in the owning feature plugin. Do not move those records into DataRegistry. Prefer scalar player_id references for new feature-owned tables and keep feature queries in the owning feature.
Feature-owned services are the supported sharing boundary for that data. For example, a messaging feature may ask the Vanish feature whether a playerId is hidden, but it should not read or join the vanish table directly.
Authenticated GitHub Packages access may be required for private HauntedMC dependencies. Configure repository id github in ~/.m2/settings.xml, then run:
# Fast reactor verification: unit tests, Checkstyle, coverage and dependency hygiene.
./mvnw -B -ntp verify
# Adds the MySQL integration suite. It creates the schema from the production Hibernate
# mappings before exercising the public DataRegistry API.
./mvnw -B -ntp -Pintegration-tests verify
# Builds the bundled Paper and Velocity artifacts, then boots each in the real target
# platform with a consumer compiled only against dataregistry-api. This suite also runs
# the fast reactor checks; it does not include the MySQL integration suite.
./mvnw -B -ntp -Pplatform-acceptance verify
# Full local release gate: fast checks, MySQL integration, and Paper/Velocity acceptance.
./mvnw -B -ntp -Pintegration-tests,platform-acceptance verify
# Shell validation used by CI for the repository maintenance scripts.
shellcheck update_version.sh dataregistry-platform-acceptance/run-platform-acceptance.sh
# Preview a patch release bump without changing files, committing, or tagging.
./update_version.sh --dry-run patchThe integration and platform suites need a reachable Docker daemon. The platform suite additionally needs curl, jq, sha256sum, jar, and exactly Java 25. Java 26 is intentionally rejected because the currently supported DataProvider/Hibernate runtime is qualified against Java 25. The platform suite downloads the configured Paper and Velocity runtime builds, checks their SHA-256 values, provisions MySQL 8.4, checks public API reads and writes, reloads DataProvider configuration, and requires clean DataRegistry and Hikari shutdown. Set PLATFORM_ACCEPTANCE_KEEP_WORK_DIRECTORY=true to retain server logs after a local run.
The tag release workflow runs both profiles against the exact tagged reactor before Maven deployment and independently verifies that the vX.Y.Z tag matches the Maven project version. This keeps fast checks, MySQL schema compatibility, and real bundled-plugin boot checks in the release gate.
Build output:
Deploy the bundled platform JAR only. It embeds the platform's relocated core implementation while retaining the public DataRegistryApi namespace. Do not deploy dataregistry-core as a separate server plugin and do not add it as a dependency to feature plugins.
This project is licensed under the GNU Affero General Public License v3.0.
| Back | FazBrowse Home | New Git URL |