Skip to content

API Cheat Sheet

Petrus Pradella edited this page Jun 23, 2026 · 5 revisions

API Cheat Sheet

What this page covers: a one-screen index of the public surface — every factory, every Repository method, the descriptor builder, the query/index API, the capability interfaces, and the manager add-on. Skim it to find the call you need, then click through for the details. Signatures are real; semantics are paraphrased — the linked guide pages and the Javadoc are canonical.

📌 Note — every I/O call returns a CompletableFuture. Examples use .join() for brevity, but you compose with thenApply/thenCompose; there are no blocking variants. See The Async API.


The smallest end-to-end

EntityDescriptor<UUID, PlayerData> PLAYERS = EntityDescriptor.builder(UUID.class, PlayerData.class)
        .collection("players")
        .keyExtractor(PlayerData::getUuid)
        .codec(new JacksonJsonCodec<>(PlayerData.class))
        .build();

Storage storage = Storages.createH2(new SqlConfig("jdbc:h2:mem:demo", "", ""));
storage.init().join();
Repository<UUID, PlayerData> repo = storage.repository(PLAYERS);

repo.save(new PlayerData(id, "Alice", 100)).join();
Optional<PlayerData> alice = repo.find(id).join();
storage.close().join();

Storages — the factory

Typed builders return concrete types, so capability interfaces are reachable without a cast.

Call Returns Notes
Storages.createSQL(SqlConfig) SqlStorage MySQL / MariaDB dialect
Storages.createPostgreSQL(SqlConfig) PostgreSqlStorage PostgreSQL dialect
Storages.createH2(SqlConfig) H2SqlStorage jdbc:h2:mem: / file: / tcp:
Storages.createMongo(MongoConfig) MongoStorage tx needs a replica set
Storages.createLocalFile(LocalFileConfig) LocalFileStorage one file per entity
Storages.createInMemory() InMemoryStorage ephemeral
Storages.create(StorageConfig) Storage dispatches by config type

⚠️ GotchaStorages.create(SqlConfig) always picks the MySQL/MariaDB dialect. For PostgreSQL or H2 call createPostgreSQL / createH2 explicitly. See Gotchas & Pitfalls and Choosing a Backend.


Storage — lifecycle + repository factory

CompletableFuture<Void>    init();      // idempotent
CompletableFuture<Void>    close();     // idempotent
CompletableFuture<Boolean> health();
<K, V> Repository<K, V>    repository(EntityDescriptor<K, V> descriptor);   // cached per collection

repository(sameDescriptor) returns the same object each time. See Architecture Overview.


Repository<K, V> — typed CRUD

Method Returns Notes
find(K key) CompletableFuture<Optional<V>> empty when absent
findMany(Collection<K> keys) CompletableFuture<List<V>> missing keys omitted
save(V entity) CompletableFuture<Void> upsert (insert or replace)
saveAll(Collection<V> entities) CompletableFuture<Void> batched
delete(K key) CompletableFuture<Boolean> true if it existed
exists(K key) CompletableFuture<Boolean>
count() CompletableFuture<Long>
versions(Collection<K> keys) CompletableFuture<Map<K, Long>> cheap key+version read; 0 if unversioned/H2; powers cache-sync polling
all() CompletableFuture<Stream<V>>
findBy(String fieldPath, Object value) CompletableFuture<List<V>> declared index only
query(Query query) CompletableFuture<List<V>> declared indexes only

See CRUD Operations and Indexing & Queries.


EntityDescriptor builder

EntityDescriptor.builder(Class<K> keyType, Class<V> type)   // keyType is FIRST
        .collection(String name)               // must match ^[a-zA-Z][a-zA-Z0-9_]*$
        .keyExtractor(Function<V, K> getKey)
        .codec(Codec<V> codec)
        .index(IndexHint hint)                 // repeatable
        .versioned()                           // entity implements Versioned
        .version(Function<V, Long> get, BiConsumer<V, Long> set)   // explicit accessors
        .build();

⚠️ Gotcha — the builder takes keyType first, then the entity type — both are Class, so a flip compiles. EntityDescriptor.builder(UUID.class, Player.class), never the reverse. See Defining Entities.


IndexHint factories + @Indexed

Factory Field type Annotation equivalent
IndexHint.string(path) STRING @Indexed on a String
IndexHint.integer(path) INT @Indexed on an int/Integer
IndexHint.bigInt(path) LONG @Indexed on a long/Long
IndexHint.decimal(path) DOUBLE @Indexed on a double
IndexHint.bool(path) BOOLEAN @Indexed on a boolean
IndexHint.timestamp(path) TIMESTAMP @Indexed on a date/Instant

Order helpers: IndexHint.descending(path), hint.asDescending() / .asAscending(). @Indexed(path = "...", type = String.class, order = IndexHint.Order.DESCENDING) on a field is merged by build(). See Indexing & Queries.


Query — composing conditions

Query.eq(String fieldPath, Object value)
Query.range(String fieldPath, Object fromInclusive, Object toInclusive)   // inclusive; null = open end
Query.in(String fieldPath, Object... values)                             // also Collection<?> overload
query.and(Query other)                                                    // AND only — no native OR
repo.query(Query.eq("world", "world_nether")
        .and(Query.range("score", 1000, null))).join();   // world == … AND score >= 1000

⚠️ Gotcha — querying an undeclared field throws IllegalArgumentException on every backend, LocalFile included. Union two queries client-side for OR. See Indexing & Queries.


Codecs

new JacksonJsonCodec<>(Type.class)        // compact JSON (the database default)
JacksonJsonCodec.pretty(Type.class)       // indented JSON (human-readable LocalFile)
new JacksonYamlCodec<>(Type.class)        // YAML — LocalFile only

Codec<V>: encode / decode / contentType() / isJsonCodec() / fileExtension(). SQL, Mongo and InMemory require a JSON codec; only LocalFile accepts YAML. See Codecs.


Capability interfaces (check with instanceof)

if (storage instanceof TransactionalStorage tx) {
    tx.inTransaction(scope -> {                     // scope.repository(d) / scope.rollback()
        Repository<UUID, Account> accounts = scope.repository(ACCOUNTS);
        return accounts.save(updated);
    }).join();
}

if (storage instanceof SchemaAwareStorage schema) {
    schema.register(new V001_CreateAuditLog()).migrate().join();
}
Interface Method Backends
tx.TransactionalStorage inTransaction(scope -> future) SQL (incl. H2), Mongo (replica set), InMemory (no isolation)
schema.SchemaAwareStorage register(...).migrate() SQL, Mongo, LocalFile
changefeed.ChangeFeedStorage subscribe(listener) + originId() Mongo, PostgreSQL, InMemory (others poll)

Migration base classes: SqlMigration (override upScript()), MongoMigration (override executeOnDatabase(MongoDatabase)), LocalFileMigration (override executeOnStorage(LocalFileStorage)); MigrationContext.getNativeClient(Type.class). See Transactions and Schema Migrations.


Optimistic locking

@OptimisticLock private Long lockVersion;        // preferred — auto-wired at build()
// or: .versioned()  (entity implements Versioned)
// or: .version(Account::getLockVersion, Account::setLockVersion)

Version starts at 0, +1 per update; mismatch → OptimisticLockException (surfaces as the cause of a CompletionException). Enforced by MySQL/MariaDB, PostgreSQL, Mongo; H2 silently degrades; LocalFile and InMemory don't enforce it. See Optimistic Locking.


StorageTransfer — cross-backend copy

TransferReport report = StorageTransfer.builder()
        .from(src).to(dst)
        .descriptor(PLAYERS)                       // or descriptor(srcDesc, dstDesc) to rename/recodec
        .batchSize(500)                            // default 500
        .errorPolicy(ErrorPolicy.FAIL_FAST)        // or CONTINUE / SKIP_EXISTING
        .applyTargetMigrations(true)
        .failIfTargetCollectionNotEmpty(true)
        .verifyCounts(true)
        .build().execute().join();

report.success(); report.errors(); report.totalEntities(); report.durationMs();

The future does not complete exceptionally for expected failures — they collect in report.errors(). See Moving Data Between Backends.


Logging

StorageLogConfig.defaults();   // WARN floor — routine silent, failures visible
StorageLogConfig.silent();     // only the unsuppressible ERROR floor
StorageLogConfig.verbose();    // DEBUG
StorageLogConfig.trace();

cfg.level(StorageLogTopic.WRITE, StorageLogLevel.DEBUG)
   .mute(StorageLogTopic.INDEX)
   .includeKeys(true);          // includeKeys / includeValues / includeQueryValues — default false

StorageLogSinks.installDefault(event -> logger.info(event.format()));   // host bridge

Topics: LIFECYCLE SCHEMA INDEX MIGRATION WRITE DELETE READ QUERY TRANSACTION TRANSFER HEALTH. CLI: -Deverydatabase.log.level=info|debug|trace. See Logging & Diagnostics.


Manager add-on (everydatabase-manager)

RefRegistry refRegistry = new RefRegistry();                            // per-context; no global default

JacksonJsonCodec<Guild>      codec   = refRegistry.codec(Guild.class);  // ref-aware, bound to 'refRegistry'
CachingManager<UUID, Guild>  guilds  = refRegistry.manager(GUILDS, storage, CachePolicy.always());
Ref<UUID, Guild>             ref     = refRegistry.ref(guildId, Guild.class); // bound ref

Ref<K, V> (serializes as its key):

Call Blocks? I/O? Returns
ref.peek() no never Optional<V> — cache-only
ref.resolve() no on miss CompletableFuture<Optional<V>>
ref.join() yes on cold miss V or null

Build: Ref.of(key, type, null) (unbound — store-only), Ref.of(key, type, registry) / refRegistry.ref(key, type) (bound), Ref.empty(type). Derive: ref.withTtl(Duration), ref.withPolicy(CachePolicy), ref.withRegistry(RefRegistry).

CachingManager<K, V>: resolve(key) · peek(key) · getAll(keys) (N+1 antidote) · preloadAll() · saveAndCache(v) · saveAllAndCache(values) (batch write-through → BatchSaveReport) · seedIfAbsent(key, v) (cache, no I/O - keep-first) · flushDirty() (write-back, batch flush → BatchSaveReport) · deleteAndEvict(key) · invalidate(key) · evict(key) · invalidateAll() · clearCache() · purgeExpired() · repository() · cachedSize().

Write-back (opt-in mutate-in-memory / flush-later): an entity opts in either by implementing IDirtyable (boolean isDirty() · void markClean() · void markDirty()) or by annotating a boolean field with @DirtyFlag (keep the flag transient + @JsonIgnore; pick one, not both). Dirty wins - while dirty a cell is always served and never reloaded over (not by TTL, not by invalidate). seedIfAbsent + mutate + flushDirty() is the loop. BatchSaveReport<K> lists failures only: isEmpty() · hasFailures() · failures() · conflictedKeys() (cells evicted) · erroredKeys() (cells kept); each KeyOutcome<K> has key() · status() (SAVED/CONFLICT/ERROR) · error(). See Caching Managers.

Parent registries: new RefRegistry(parent) chains to a parent - parent(), resolver(type) falls back up the chain (register/manager stay local), isRegistered(type) (local) vs isRegisteredInChain(type) (walks the chain). Private-then-shared: keep the chain shallow (yours -> shared).

Freshness vs capacity: CachePolicy.always() / ttl(Duration) / noCache() (per-read, per-ref overridable) — CacheOptions.builder().policy(...).maxSize(n).build() (LRU, store-level). @RefPolicy(ttlSeconds = …, noCache = …) per field.

⚠️ Gotcha — a bare Ref.of(key, type, null) is unbound: fine to store (writes just the key), but resolve()/peek() fail fast until bound to a registry. See Caching & References.


Cross-process cache sync (manager.sync)

Keep CachingManager caches fresh across instances — push where the backend supports it, poll where it doesn't, behind one API.

CacheSync sync = CacheSync.attach(storage)     // or CacheSync.auto() for managers on different backends
        .pollEvery(Duration.ofSeconds(10))     // required only if `storage` can't push
        .bind(guilds)                          // bind(manager) or bind(manager, str -> myKey(str))
        .includeOwnOrigin()                    // optional: process this instance's own events too
        .onError(t -> log.warn(t))             // optional: surface key-parse / poll errors
        .start();
sync.close();                                  // idempotent; pollOnce() for a synchronous cycle (tests)

// pull primitive, used directly:
PollingCacheSync.every(Duration.ofSeconds(10)).bind(guilds).start();

SAVE → invalidate, DELETE → evict. Built-in key parsers: String/UUID/Long/Integer (pass an explicit parser for composite/record keys). Push backends: Mongo (Change Streams), PostgreSQL (LISTEN/NOTIFY), InMemory; the rest poll via Repository.versions(...). Delivery is at-least-once and (Postgres) lossy — keep a TTL as the safety net. See Cross-Process Cache Sync.


See also

Clone this wiki locally