Storage API
Modules get two persistence handles, both namespaced to the module’s id so that collection and key names cannot collide across modules:
ModuleDataStore— Mongo-backed document store, accessed throughModuleContext.requireMongoStorage().PlatformRedisStorage— Redis/Valkey key/value store, accessed throughModuleContext.requireRedisStorage().
Daemon-host contexts return empty for both — daemons have no Mongo binding in v1.
What you’ll learn
ModuleDataStoreCRUD, indexing, and transactions.PlatformRedisStorageget/set/increment/TTL.- The collection-prefix and key-prefix contracts.
ModuleDataStore — Mongo
The interface lives at
me.prexorjustin.prexorcloud.api.module.data.ModuleDataStore.
Namespacing
String collectionPrefix(); // e.g. "mod_stats_aggregator_"void ensureCollection(String name);void createIndex(String collection, IndexSpec index);Every collection name you pass through find, insert*, etc. is
qualified by the prefix. Calling insertOne("sessions", ...) writes to
mod_<id>_sessions under the hood. Modules cannot read other modules’
collections through this handle.
Insert
<T> String insertOne(String collection, T document);<T> int insertMany(String collection, List<T> documents);Documents are serialised through Jackson — your records / DTOs round-trip
via the same ObjectMapper exposed on ModuleContext.json().
insertOne returns the generated id as string.
Read
<T> Optional<T> findOne(String collection, Query filter, Class<T> type);<T> List<T> find(String collection, Query filter, Sort sort, int limit, Class<T> type);<T> List<T> find(String collection, Query filter, Sort sort, int limit, int skip, Class<T> type);long count(String collection, Query filter);Query is a fluent builder — see below.
Update
int updateOne(String collection, Query filter, Update update);int updateMany(String collection, Query filter, Update update);boolean upsertOne(String collection, Query filter, Update update); // true = insertedDelete
boolean deleteOne(String collection, Query filter); // true = a doc was deletedint deleteMany(String collection, Query filter);Transaction
@FunctionalInterfaceinterface TransactionWork { void execute(ModuleDataStore txStore) throws Exception;}
void withTransaction(TransactionWork work);Multi-document ACID transaction. The txStore argument is a
short-lived store bound to the transaction; do not capture it past
the lambda.
Query builder
Query.where("status").eq("QUEUED").and("to_uuid").in(uuids)
Query.or( Query.where("from_uuid").eq(a).and("to_uuid").eq(b), Query.where("from_uuid").eq(b).and("to_uuid").eq(a))Driver-agnostic; the implementation translates conditions into Mongo filter documents under the hood.
PlatformRedisStorage — Redis / Valkey
The interface lives at
me.prexorjustin.prexorcloud.api.module.platform.PlatformRedisStorage.
String keyPrefix(); // e.g. "mod:stats:"default String qualify(String key); // keyPrefix + keyOptional<String> get(String key);void set(String key, String value);void set(String key, String value, Duration ttl);long increment(String key);long decrement(String key);boolean delete(String key);Same namespacing pattern as Mongo — every key you read or write is prefixed. Use Redis for short-lived counters, rate limits, distributed locks, and per-instance caches; use Mongo for durable state.
Example
public final class StatsRepository {
private final ModuleDataStore mongo;
public StatsRepository(ModuleDataStore mongo) { this.mongo = mongo; mongo.ensureCollection("sessions"); mongo.ensureCollection("player_stats"); mongo.createIndex("sessions", IndexSpec.of("player_id", IndexSpec.Order.ASC)); }
public void recordSession(SessionRecord record) { mongo.insertOne("sessions", record); }
public Optional<PlayerStat> playerStat(UUID playerId) { return mongo.findOne("player_stats", Query.where("playerId").eq(playerId.toString()), PlayerStat.class); }
public List<SessionRecord> recentSessionsForPlayer(UUID playerId, int limit) { return mongo.find("sessions", Query.where("player_id").eq(playerId.toString()), Sort.desc("quit_at"), limit, SessionRecord.class); }}The repository takes the store as a constructor argument — no field injection.
Manifest declaration
storage: mongo: true redis: true limits: mongoDocuments: 500000 redisKeys: 100000The controller refuses to load a module that calls
requireMongoStorage() without mongo: true in its manifest.
Next up
- ModuleContext — how the storage handles are obtained.
- module.yaml —
storage:schema. - Concepts → Module storage