Valkey 9.2 commands
Valkey 9.2 adds opt-in missing-key replies and server administration commands. Respire tests
these against valkey/valkey:9.2.0-rc1-alpine under RESP2 and RESP3. This is a release candidate;
the compatibility matrix will need a new pin when the stable release is available.
Distinguish missing keys
SISMEMBER XX returns -1 for a missing key. ContainsIfExistsAsync maps this to null,
0 to false, and 1 to true:
bool? membership = await redis.Sets.ContainsIfExistsAsync("members", "alice");
RangeIfExistsAsync and RangeIfExistsWithScoresAsync send ZRANGE XX. They return null
when the sorted-set key is absent, an empty array when an existing key has no members in the
requested rank range, and an owned array otherwise. Both support descending, typed member
deserialization, and batches or transactions through methods without the Async suffix.
var members = await redis.SortedSets.RangeIfExistsAsync("ranking", start: 0, stop: 9);
var scored = await redis.SortedSets.RangeIfExistsWithScoresAsync("ranking");
var binary = await redis.SortedSets.RangeIfExistsAsync<byte[]>("ranking");
Existing ContainsAsync, RangeAsync, and RangeWithScoresAsync keep their existing reply
types and omit XX. Older servers keep working with these APIs. The new methods preserve
unsupported-option errors from older servers; there is no fallback that loses missing-key information.
For BYSCORE, BYLEX, or legacy range commands with XX, use the matching descriptor through
ExecuteAsync and check RespireResult.IsNull before reading the array.
The empty XPENDING summary is [0, null, null, null] on Valkey 9.2. Its release notes correct
the reply schema rather than introducing an incompatible wire reply. PendingSummaryAsync
already accepts this shape, RESP2 null arrays, and an empty consumers array. It returns zero
pending entries, null minimum and maximum IDs, and an empty consumers array.
Replace a key in another database
bool moved = await redis.Keys.MoveAsync("key", database: 2, replace: true);
This sends MOVE key 2 REPLACE. The overload also works in batches and transactions.
replace: false omits REPLACE. MOVE requires a standalone server and rejects negative database
numbers and Cluster clients. Key prefixes apply to the moved key.
Configuration metadata and ACL roles
var configuration = await redis.Server.ConfigInfoAsync(["maxmemory", "append*"]);
var digest = await redis.Server.AclDigestAsync();
var roles = await redis.Server.AclRolesAsync();
var role = await redis.Server.AclGetRoleAsync("reader");
ConfigInfoAsync returns owned RespireConfigurationInfo records with names, server-defined
types and flags, optional aliases, enum values, and numeric bounds. Bounds remain decimal
strings so unsigned values larger than long.MaxValue retain their exact value. Unknown fields
remain available in AdditionalFields.
AclGetRoleAsync returns null for an unknown role. Its Root selector and extra Selectors
retain commands, binary key/channel rules, and unknown fields, including Valkey's databases
rule. Users contains owned binary usernames. AclDigestAsync and AclRolesAsync also return
owned bytes.
With AllowAdmin = true, create or update roles with AclSetRoleAsync, and delete them with
AclDeleteRolesAsync. Rules are separate binary-safe tokens, use Valkey's ACL syntax, and
are applied in order. Role identifiers, patterns, and rules receive no key prefix. These APIs
operate on one execution node; they do not broadcast changes, persist ACL files, or replicate
configuration to other Cluster nodes.
SET IFNE already uses RespireValueCondition; no new condition type is needed.
The regenerated command catalog also includes Valkey 9.2 PathHash descriptors under
RespireCommands.PathHash. Descriptor availability does not imply support on older servers.
See the Valkey 9.2 release notes and the server compatibility matrix.