Shared-state adapters
These adapters implement independent consistency domains. Choose a backend per contract rather than treating “distributed state” as one database.
Account version
AccountVersionStore supplies a generation used to invalidate old sessions; MutableAccountVersionStore changes it. Built-ins are:
MemoryAccountVersionStorefor one process;RedisAccountVersionStorewith theredisfeature;SqlAccountVersionStorefor PostgreSQL or MySQL withsql.
The SQL adapter exposes ensure_schema; run migrations under controlled startup or deployment ownership rather than concurrently from every replica.
Usage example
use std::sync::Arc;
use elura::adapters::account_version::RedisAccountVersionStore;
use elura::prelude::*;
let versions = Arc::new(
RedisAccountVersionStore::connect(redis_url, "game:account-version").await?,
);
let gateway = Gateway::new(gateway_config)
.account_version_store(versions, AccountVersionSettings::default());Use SqlAccountVersionStore::connect_postgres or connect_mysql when account state already lives in SQL. Call ensure_schema once under migration ownership.
Online directory
OnlineDirectory manages Session leases, lookup, groups, and duplicate-login fencing. OnlineStatsReader returns per-region/per-realm Session and distinct-user totals. OnlineBackend combines both capabilities and is implemented automatically when a type provides both narrower traits.
Lease TTL must tolerate short pauses without keeping dead sessions online for too long. KickExisting also requires a compatible session-control transport.
See the online presence API for complete contracts, Gateway installation, online counts, lifecycle observers, and custom backend guidance.
Usage example
use std::{sync::Arc, time::Duration};
use elura::adapters::online::RedisOnlineDirectory;
use elura::prelude::*;
let directory = Arc::new(RedisOnlineDirectory::connect(
redis_url,
"game:online",
Duration::from_secs(60),
).await?);
let online_config = GatewayOnlineConfig::new(
"gateway-1",
Duration::from_secs(60),
Duration::from_secs(20),
DuplicateLoginMode::AllowMultiple,
);
let gateway = Gateway::new(gateway_config)
.online_directory(directory, online_config);See Online presence for Session lookup, totals, groups, lifecycle observers, and KickExisting configuration.
OTP storage
OtpStore atomically creates challenges and verifies/consumes attempts. MemoryOtpStore is process-local; RedisOtpStore is required when multiple API replicas issue or verify the same challenge namespace.
Storage protects atomicity and cooldown. API-level IP, recipient, and global rate limits remain an application responsibility. See the OTP provider.
Usage example
use std::time::Duration;
use elura::adapters::otp::RedisOtpStore;
use elura::core::otp::{OtpCreateResult, OtpRecord, OtpStore};
let store = RedisOtpStore::connect(redis_url, "game:otp").await?;
let result = store
.create(
OtpRecord {
subject_key: "email:user@example.com".into(),
purpose: "login".into(),
code_digest: digest.to_vec(),
},
Duration::from_secs(300),
Duration::from_secs(60),
)
.await?;
assert!(matches!(result, OtpCreateResult::Stored | OtpCreateResult::Cooldown));Store a cryptographic digest, never the plaintext OTP. Pass the same store to the application Provider that issues and verifies challenges.
Ticket replay
ReplayStore prevents a one-time Gateway ticket from being accepted twice. MemoryReplayStore is correct only when all verification for a ticket namespace hits one process. RedisReplayStore shares replay state across Gateway replicas.
Do not horizontally scale ticket verification with independent memory stores; the same ticket can otherwise be accepted by multiple replicas.
Usage example
use std::sync::Arc;
use elura::adapters::replay::RedisReplayStore;
use elura::prelude::*;
let replay = Arc::new(
RedisReplayStore::connect(redis_url, "game:ticket-replay").await?,
);
let gateway = Gateway::new(gateway_config).replay_store(replay);For a single Gateway process, MemoryReplayStore is the zero-dependency alternative. Switch to Redis before multiple Gateways can accept the same ticket namespace.