Class KeyValueStore
-
- All Implemented Interfaces:
public final class KeyValueStoreWrapper around NATS KeyValue that provides type-safe, coroutine-friendly operations
Values are serialized through natsy's codec registry. Every operation reports failure by throwing a eu.vstoyanov.natsy.exception.NatsyException; a
nullreturn means the key is absent, never that the read failed.Example:
val kv = js.keyValue("user-prefs") { maxHistoryPerKey = 5 ttl = 24.hours } // Keys are subject-shaped: letters, digits and - . / = _ only, dot-separated. A key with a // character outside that set - a colon, say - is rejected by an IllegalArgumentException. kv.put("user.123.theme", "dark") val theme = kv.get<String>("user.123.theme") ?: "light" kv.watch<String>("user.123.*").collect { entry -> println("${entry.key} changed to ${entry.value}") }
-
-
Field Summary
Fields Modifier and Type Field Description private final StringbucketName
-
Method Summary
Modifier and Type Method Description final StringgetBucketName()Get the name of the bucket final <T extends Any> Longput(String key, T value)Put a typed value for a key final <T extends Any> Tget(String key)Get a typed value for a key final <T extends Any> Tget(String key, Long revision)Get a specific revision of a typed value final <T extends Any> Longcreate(String key, T value)Create a new key only if it does not already exist final <T extends Any> Longupdate(String key, T value, Long expectedRevision)Update a key only if the expected revision matchesThe compare-and-swap half of the store: expectedRevision not matching is a normal outcome of a read-modify-write loop, reported as eu.vstoyanov.natsy.exception.NatsyConflictException and not logged - the caller re-reads and retries. final Unitdelete(String key)Delete a key (soft delete - places a delete marker) final Unitdelete(String key, Long expectedRevision)Delete a key only if the expected revision matches final Unitpurge(String key)Purge all history for a key (hard delete) final Unitpurge(String key, Long expectedRevision)Purge a key only if the expected revision matches final Flow<String>keys()Get all keys in the bucketA failure reaches the collector rather than completing the flow: an empty result means the bucket is empty, and nothing else. final Flow<String>keys(String filter)Get keys matching a filter pattern final <T extends Any> Flow<KeyValueEntry<T>>watch(String key, Set<KvWatchOption> options, Long fromRevision, DecodeErrorStrategy onDecodeError)Watch for changes to a specific keyThe watch opens by replaying the latest revision of every matching key - not the whole history ( io/nats/client/impl/NatsKeyValueWatchSubscription.java:41) - and then emits a KeyValueEntry on every change.final <T extends Any> Flow<KeyValueEntry<T>>watch(List<String> keys, Set<KvWatchOption> options, Long fromRevision, DecodeErrorStrategy onDecodeError)Watch several keys, or key patterns, through one subscriptionOne consumer covers them all, so the entries arrive interleaved in server order rather than as several independent flows. final <T extends Any> Flow<KeyValueEntry<T>>watchAll(Set<KvWatchOption> options, Long fromRevision, DecodeErrorStrategy onDecodeError)Watch for changes to all keys in the bucketThe watch buffer is unbounded so no revision is dropped on its way to the collector. final <T extends Any> Flow<KvWatchEvent<T>>watchEvents(List<String> keys, Set<KvWatchOption> options, Long fromRevision, DecodeErrorStrategy onDecodeError)Watch, and be told when the opening replay is overThe same subscription watch and watchAll open, with one more element in it: every revision arrives as KvWatchEvent.Entry, and KvWatchEvent.InitialSyncComplete marks the point where the bucket's existing contents end and live changes begin. final <T extends Any> Flow<KeyValueEntry<T>>history(String key, DecodeErrorStrategy onDecodeError)Get the history of a key final KeyValueStatusstatus()Get the status of the bucket -
-
Method Detail
-
getBucketName
final String getBucketName()
Get the name of the bucket
-
put
final <T extends Any> Long put(String key, T value)
Put a typed value for a key
- Parameters:
key- The keyvalue- The value to store- Returns:
the revision the value was stored at
-
get
final <T extends Any> T get(String key)
Get a typed value for a key
- Parameters:
key- The key- Returns:
the value, or
nullif the key does not exist, was deleted or purged. A live key whose payload is empty is present:put(key, "")reads back as"", not asnull
-
get
final <T extends Any> T get(String key, Long revision)
Get a specific revision of a typed value
- Parameters:
key- The keyrevision- The revision to read- Returns:
the value at that revision, or
nullif it does not exist or is not a live value. An empty payload is a value, the same way it is on get
-
create
final <T extends Any> Long create(String key, T value)
Create a new key only if it does not already exist
- Parameters:
key- The keyvalue- The value to store- Returns:
the revision the value was stored at
-
update
final <T extends Any> Long update(String key, T value, Long expectedRevision)
Update a key only if the expected revision matches
The compare-and-swap half of the store: expectedRevision not matching is a normal outcome of a read-modify-write loop, reported as eu.vstoyanov.natsy.exception.NatsyConflictException and not logged - the caller re-reads and retries.
- Parameters:
key- The keyvalue- The value to storeexpectedRevision- The revision the caller believes is current- Returns:
the new revision
-
delete
final Unit delete(String key)
Delete a key (soft delete - places a delete marker)
- Parameters:
key- The key to delete
-
delete
final Unit delete(String key, Long expectedRevision)
Delete a key only if the expected revision matches
- Parameters:
key- The key to deleteexpectedRevision- The revision the caller believes is current
-
purge
final Unit purge(String key)
Purge all history for a key (hard delete)
- Parameters:
key- The key to purge
-
purge
final Unit purge(String key, Long expectedRevision)
Purge a key only if the expected revision matches
- Parameters:
key- The key to purgeexpectedRevision- The revision the caller believes is current
-
keys
final Flow<String> keys()
Get all keys in the bucket
A failure reaches the collector rather than completing the flow: an empty result means the bucket is empty, and nothing else.
- Returns:
Flow of key names
-
keys
final Flow<String> keys(String filter)
Get keys matching a filter pattern
- Parameters:
filter- Subject-like filter (e.g., "users.*" or "users.>")- Returns:
Flow of key names matching the filter
-
watch
final <T extends Any> Flow<KeyValueEntry<T>> watch(String key, Set<KvWatchOption> options, Long fromRevision, DecodeErrorStrategy onDecodeError)
Watch for changes to a specific key
The watch opens by replaying the latest revision of every matching key - not the whole history (
io/nats/client/impl/NatsKeyValueWatchSubscription.java:41) - and then emits a KeyValueEntry on every change. KvWatchOption.INCLUDE_HISTORY and KvWatchOption.UPDATES_ONLY are the two ways to start somewhere else; watchEvents is what says where that opening replay ends.The watch buffer is unbounded: a missed revision means a permanently stale view, since the server never redelivers it. On a watch of one concrete key
.conflate()bounds that memory without stalling the watcher - it fuses into the watch buffer, and where there is one key the latest element and the latest value are the same entry. It is the wrong operator as soon as a second key can arrive, including under the wildcards this overload accepts.- Parameters:
key- The key to watch (can include wildcards, e.g., "users.options- Where the watch starts and how much of each revision it carries, see KvWatchOptionfromRevision- Start at this revision instead of at the default snapshot, replaying from it inclusiveonDecodeError- What to do with a revision whose value cannot be decoded, see DecodeErrorStrategy.- Returns:
Flow of typed KeyValueEntry objects
-
watch
final <T extends Any> Flow<KeyValueEntry<T>> watch(List<String> keys, Set<KvWatchOption> options, Long fromRevision, DecodeErrorStrategy onDecodeError)
Watch several keys, or key patterns, through one subscription
One consumer covers them all, so the entries arrive interleaved in server order rather than as several independent flows. More than one key needs a server at 2.10.0 or later, which is where filtering a consumer on several subjects arrived (
io/nats/client/impl/NatsJetStreamImpl.java:99-103)..conflate()is not the operator for this flow: it keeps the latest element whatever key it belongs to, so another key's revision is what it drops.- Parameters:
keys- The keys to watch; each may include wildcards, and the list may not be emptyoptions- Where the watch starts and how much of each revision it carries, see KvWatchOptionfromRevision- Start at this revision instead of at the default snapshot, replaying from it inclusiveonDecodeError- What to do with a revision whose value cannot be decoded, see watch- Returns:
Flow of typed KeyValueEntry objects for every key named
-
watchAll
final <T extends Any> Flow<KeyValueEntry<T>> watchAll(Set<KvWatchOption> options, Long fromRevision, DecodeErrorStrategy onDecodeError)
Watch for changes to all keys in the bucket
The watch buffer is unbounded so no revision is dropped on its way to the collector. Only a
.buffer(n, DROP_OLDEST)- or another non-SUSPEND policy - bounds it, because a plain.buffer(n)fuses by taking the larger capacity and so cannot shrink an unbounded one..conflate()is the wrong operator for a whole bucket either way: it keeps the latest element whatever key it belongs to.- Parameters:
options- Where the watch starts and how much of each revision it carries, see KvWatchOptionfromRevision- Start at this revision instead of at the default snapshot, replaying from it inclusiveonDecodeError- What to do with a revision whose value cannot be decoded, see watch- Returns:
Flow of typed KeyValueEntry objects for all keys
-
watchEvents
final <T extends Any> Flow<KvWatchEvent<T>> watchEvents(List<String> keys, Set<KvWatchOption> options, Long fromRevision, DecodeErrorStrategy onDecodeError)
Watch, and be told when the opening replay is over
The same subscription watch and watchAll open, with one more element in it: every revision arrives as KvWatchEvent.Entry, and KvWatchEvent.InitialSyncComplete marks the point where the bucket's existing contents end and live changes begin. That is what makes the warm-a-cache-then-serve pattern expressible - fill from the entries, flip the readiness probe on the signal, apply what follows as updates - and it is the only place natsy surfaces jnats'
endOfData(io/nats/client/api/Watcher.java:30-34).Do not
.conflate()this flow: the signal is an element like any other, and conflation can drop the very one the flow exists for.- Parameters:
keys- The keys to watch, each of which may include wildcards;null, the default, watches the whole bucket as watchAll doesoptions- Where the watch starts and how much of each revision it carries, see KvWatchOptionfromRevision- Start at this revision instead of at the default snapshot, replaying from it inclusiveonDecodeError- What to do with a revision whose value cannot be decoded, see watch- Returns:
Flow of KvWatchEvent, entries and the one initial-sync signal
-
history
final <T extends Any> Flow<KeyValueEntry<T>> history(String key, DecodeErrorStrategy onDecodeError)
Get the history of a key
- Parameters:
key- The keyonDecodeError- What to do with a revision whose value cannot be decoded, see watch- Returns:
Flow of typed KeyValueEntry objects representing the history
-
status
final KeyValueStatus status()
Get the status of the bucket
- Returns:
the bucket status
-
-
-
-