componentized/directory

componentized:valkey

interface dev

A WASM component client for Valkey (and Redis).

interface connection componentized:valkey/connection@0.3.0-dev

RESP request/reply over a pair of value streams, independent of the transport carrying them.

A transport and a connection are wired together with their streams: take the replies the transport receives, establish the connection with them, hand the requests the connection returns to the transport to send, then await the connection. See tcp.connect for a transport that does this for its caller.

The connection is the mechanics of talking to a server: sending commands and pairing replies with them, subscriptions, push messages, and transactions. Higher level, typed operations are functions in the commands interface that borrow the connection.

Types

use record connect-optssince 0.3.0-dev

from componentized:valkey/types@0.3.0-dev

Options for establishing a connection.

  • auth: option<tuple<string, string>>

    The username and password to authenticate with.

  • client-name: option<string>

    The name of the connection, see CLIENT SETNAME.

  • keep-alive-interval: option<duration-milliseconds>

    Enables keep-alive pings at this interval, see connection.set-keep-alive.

use record duration-millisecondssince 0.3.0-dev

from componentized:valkey/types@0.3.0-dev

A duration in whole milliseconds, e.g. PX milliseconds.

  • px: u64

use variant error-codesince 0.3.0-dev

from componentized:valkey/types@0.3.0-dev

connection The errors of a connection and of every command interface.

  • closed: option<string>

    The transport closed while a reply was outstanding, or before the command was sent. Holds the transport's error when it did not close gracefully.

  • client: string

    Client errors are semantic issues with input/output values, e.g. a reply that does not have the shape the command expects.

  • resp: resp-error

    RESP errors are encoding issues converting values to/from bytes.

  • server: string-or-bytes

    Server errors are Error or BulkError RESP values returned directly from the server. An Error is a string, a BulkError a string when it is UTF-8, bytes otherwise.

  • lagged

    The reader of a stream fell too far behind and its stream was closed, rather than holding up the connection. What the stream missed is lost.

use variant resp-valuesince 0.3.0-dev

from componentized:valkey/types@0.3.0-dev

use variant string-or-bytessince 0.3.0-dev

from componentized:valkey/types@0.3.0-dev

  • string: string

    utf-8 decoded bytes.

  • bytes: bytes

    bytes that cannot be represented as a utf-8 string.

use variant subscription-eventsince 0.3.0-dev

from componentized:valkey/types@0.3.0-dev

What a subscription's stream carries, in the order it happened.

use variant subscription-targetsince 0.3.0-dev

from componentized:valkey/types@0.3.0-dev

What a subscription is subscribed to, see connection.subscription.

  • channel: string

    A channel, see SUBSCRIBE.

  • pattern: string

    A pattern of channels, see PSUBSCRIBE.

  • shardchannel: string

    A shard channel, see SSUBSCRIBE.

resource connectionsince 0.3.0-dev

  • static async functionnew: static async func( replies: stream<resp-value>, closed: future<result<_, error-code>>, opts: option<connect-opts>, ) -> tuple<stream<resp-value>, future<result<connection, error-code>>>since 0.3.0-dev

    Establishes a connection over a transport.

    replies are the values the transport receives from the server, and closed resolves when the transport closes, to ok for a graceful close or to the error otherwise, see tcp.transport.receive. Returns the requests for the transport to write to the server, in order, and the connection once it is established.

    The connection sends HELLO 3, with the auth and client-name of opts, as its first command. This pins the connection to RESP3 and confirms the server can be reached. The future resolves to the connection once HELLO succeeds, or to the error that stopped it. The rest of opts is applied before the connection is returned.

    It then reports the library to the server, CLIENT SETINFO LIB-NAME componentized:valkey and LIB-VER with the version of the package, ignoring an error from a server that does not know CLIENT SETINFO. They survive reset, client-setinfo replaces them.

    Async so the connection keeps working between commands after it returns, e.g. to read push messages and to send keep-alive pings.

  • functionserver-properties: func() -> list<tuple<string, resp-value>>since 0.3.0-dev

    The server's properties from the reply to HELLO, e.g. server, version and mode.

  • async functionexchange: async func( command: list<string-or-bytes>, ) -> result<resp-value, error-code>since 0.3.0-dev

    Sends a command and waits for its reply.

    Several commands may be in flight at once, replies are paired with commands in the order the commands were sent.

    For SUBSCRIBE, PSUBSCRIBE, SSUBSCRIBE and their UNSUBSCRIBE counterparts, prefer a subscription, which owns its targets' messages. Once the connection has one, it alone sends these commands, and they fail with a client error before anything is sent, here, with exchange-all, emit and transaction.

    RESET, HELLO and CLIENT REPLY always fail with a client error. See reset and server-properties, CLIENT REPLY would stop replies from pairing with commands, see emit.

    A command has at least one word, an empty command fails with a client error.

  • async functionexchange-all: async func( commands: list<list<string-or-bytes>>, ) -> list<result<resp-value, error-code>>since 0.3.0-dev

    Sends the commands one after another, with no other command sent between them, and waits for every reply.

    Each command has its own result, in order, as if each was sent with exchange. Commands sharing a connection may otherwise be interleaved with other callers' commands, which matters for a sequence that must not be interrupted, e.g. MULTI, its commands and EXEC.

  • async functionemit: async func( command: list<string-or-bytes>, ) -> result<_, error-code>since 0.3.0-dev

    Sends a command without waiting for its reply, resolving once the command is written.

    The server still replies, the reply is discarded when it arrives and an error it reports is not returned. An implementation may instead ask the server not to reply, e.g. with CLIENT REPLY SKIP.

    A command has at least one word, an empty command fails with a client error.

  • async functionwatch: async func(keys: list<string>) -> result<_, error-code>since 0.3.0-dev

    Marks the keys to watch, EXEC aborts the next transaction when any of them changed.

    Watched keys belong to the connection, and EXEC, DISCARD or UNWATCH from any caller sharing it clears them. Use a connection of your own for WATCH, the reads it guards, and the transaction.

    WATCH key [ key ... ] https://valkey.io/commands/watch/

    At least one key, no keys fails with a client error.

  • async functionunwatch: async func() -> result<_, error-code>since 0.3.0-dev

    Forgets all the watched keys.

    UNWATCH https://valkey.io/commands/unwatch/

  • async functiontransaction: async func( commands: list<list<string-or-bytes>>, ) -> result<option<list<resp-value>>, error-code>since 0.3.0-dev

    Runs the commands as a transaction, MULTI, each command, then EXEC, with no other command sent between them, see exchange-all.

    Returns none when a watched key changed and the transaction was aborted, otherwise the reply of each command in order. A command that fails while running is an error value in its place, the others still ran. A command rejected while queuing, e.g. an unknown command, fails the transaction with that error, none of the commands ran.

    MULTI and EXEC are only sent as part of a transaction, the commands between them reply QUEUED rather than their reply.

    MULTI, EXEC https://valkey.io/commands/multi/ https://valkey.io/commands/exec/

  • functionsubscription: func() -> result<subscription, error-code>since 0.3.0-dev

    A new subscription, subscribed to nothing yet, see the subscription's subscribe.

    From the first subscription, the connection owns its subscriptions: SUBSCRIBE, PSUBSCRIBE, SSUBSCRIBE and their UNSUBSCRIBE counterparts sent with exchange and the like fail with a client error, so the server's subscriptions stay in step with the connection's. Subscriptions made with exchange before are unsupported. They may keep delivering to dead-letters, the exact behavior is undocumented.

    Fails with a client error when the connection component was built without pub/sub, e.g. connection-minimal. Its push messages are then all dead letters, and SUBSCRIBE and the like may still be sent with exchange.

  • functiondead-letters: func() -> stream<resp-value>since 0.3.0-dev

    The values nothing else claims, kept apart from replies.

    Push messages that no subscription owns, e.g. client tracking invalidations, messages and confirmations of SUBSCRIBE sent with exchange, or kinds of push messages from a newer server. Values the server sends unprompted, when no command is waiting for a reply, are included, e.g. the feed of MONITOR, which are not RESP push values.

    Each call returns a new stream that receives every value arriving after the call, values arriving while no stream is open are dropped. A stream that falls too far behind is closed rather than holding up replies, the values it missed are lost.

  • async functionreset: async func() -> result<_, error-code>since 0.3.0-dev

    Resets the connection to the state of a new one, then repeats the handshake of new.

    RESET discards a transaction in progress, forgets the watched keys, unsubscribes from every channel, turns off client tracking and the like, selects database 0, and forgets the client name and the authenticated user. It also switches the connection to RESP2, so it is followed at once by HELLO 3 with the auth and client-name the connection was established with, with no other command sent between them. server-properties then holds the new reply to HELLO.

    The subscriptions are unsubscribed from every target, each with the reason reset, and stay open for new targets.

    No other command is sent until the handshake succeeds, so none runs on the connection RESET leaves. When the handshake fails, e.g. the credentials expired since the connection was established, the connection closes with its error, and the commands queued after the reset fail with it without being sent. Establish a new connection.

    RESET https://valkey.io/commands/reset/

  • functionset-keep-alive: func(interval: option<duration-milliseconds>)since 0.3.0-dev

    Sends PING when no command has been sent and no reply is outstanding for interval, none disables it. A PING that fails closes the connection. Disabled by default.

    Unlike TCP keep-alive, a PING counts as activity for the server's idle client timeout, and works over any transport.

resource subscriptionsince 0.3.0-dev

The messages of a set of channels, patterns of channels and shard channels, that changes as targets are subscribed to and unsubscribed from, see connection.subscription.

Subscriptions sharing a connection may overlap, the server stays subscribed to a target while any subscription has it. The subscription stays open until it is dropped, with or without targets or streams, then it unsubscribes from its targets. It keeps its connection open.

  • async functionsubscribe: async func( targets: list<subscription-target>, ) -> result<_, error-code>since 0.3.0-dev

    Subscribes to the targets, resolving once the server confirms each of them, see subscription-event.subscribed. Targets already subscribed to are left as they are. The server unsubscribes from a shard channel on its own when its slot moves to another node.

    Each kind of target is subscribed to with its own command, queued with no other command between them. Every command is sent, even when another fails, and those that succeed take effect, see subscription-event.subscribed and targets. The error then holds the error of each command that failed, e.g. PSUBSCRIBE: NOPERM .... A command is accepted or refused as a whole, targets of one kind are subscribed to all or none.

    SUBSCRIBE channel [ channel ... ] PSUBSCRIBE pattern [ pattern ... ] SSUBSCRIBE shardchannel [ shardchannel ... ] https://valkey.io/commands/subscribe/ https://valkey.io/commands/psubscribe/ https://valkey.io/commands/ssubscribe/

    At least one target, no targets fails with a client error.

  • functionevents: func() -> tuple<stream<subscription-event>, future<result<_, error-code>>>since 0.3.0-dev

    The events arriving after the call: targets subscribed to and unsubscribed from, and messages, in the order they happened.

    Each call returns a new stream, every open stream receives every event. Events arriving while no stream is open are kept for the next stream, up to 256, later ones are dropped. Dropping a stream does not unsubscribe, call events again for another.

    Pub/sub delivers each message at most once, to all listeners. A stream receives no events from before it opened, beyond those kept while no stream was open, and does not start with the current targets, see targets. Messages published while nothing listens are lost, e.g. before a target is subscribed to, after the connection closed, or past what a lagging stream holds. For delivery that is guaranteed, use a Valkey stream, see XADD and XREADGROUP.

    The stream closes once the subscription is dropped, the future then resolves to ok, or to the error that closed the connection. A stream that falls too far behind is closed rather than holding up the connection, its future resolves to lagged, and the events it missed, messages and changes of targets alike, are lost. The future resolves after the stream delivered what it holds, read the stream to its end first.

  • functiontargets: func() -> list<subscription-target>since 0.3.0-dev

    The targets the server confirmed, in the order they were subscribed to, until they are unsubscribed from.

    Once the connection closes, the targets confirmed until then are still listed, e.g. to subscribe to them on a new connection. subscribe and unsubscribe then fail with the error that closed the connection, and leave the targets as they are.

  • async functionunsubscribe: async func( targets: option<list<subscription-target>>, ) -> result<_, error-code>since 0.3.0-dev

    Unsubscribes from the targets, none for every target, resolving once each is unsubscribed from, see subscription-event.unsubscribed. Targets not subscribed to are left as they are. The server stays subscribed to targets other subscriptions have.

    Each kind of target is unsubscribed from with its own command, as for subscribe, e.g. the targets may be given as they are.

    UNSUBSCRIBE [ channel [ channel ... ] ] PUNSUBSCRIBE [ pattern [ pattern ... ] ] SUNSUBSCRIBE [ shardchannel [ shardchannel ... ] ] https://valkey.io/commands/unsubscribe/ https://valkey.io/commands/punsubscribe/ https://valkey.io/commands/sunsubscribe/

    A list of targets has at least one target, otherwise fails with a client error. None unsubscribes from every target.

WIT

/// RESP request/reply over a pair of value streams, independent of the transport carrying them.
///
/// A transport and a connection are wired together with their streams: take the replies the
/// transport receives, establish the connection with them, hand the requests the connection
/// returns to the transport to send, then await the connection. See `tcp.connect` for a transport
/// that does this for its caller.
///
/// The connection is the mechanics of talking to a server: sending commands and pairing replies
/// with them, subscriptions, push messages, and transactions. Higher level, typed operations are
/// functions in the `commands` interface that borrow the connection.
interface connection {
  @since(version = 0.3.0-dev)
  use types.{connect-opts, duration-milliseconds, error-code, resp-value, string-or-bytes, subscription-event, subscription-target};

  @since(version = 0.3.0-dev)
  resource connection {
    /// Establishes a connection over a transport.
    ///
    /// `replies` are the values the transport receives from the server, and `closed` resolves
    /// when the transport closes, to `ok` for a graceful close or to the error otherwise, see
    /// `tcp.transport.receive`. Returns the requests for the transport to write to the server,
    /// in order, and the connection once it is established.
    ///
    /// The connection sends `HELLO 3`, with the `auth` and `client-name` of `opts`, as its first
    /// command. This pins the connection to RESP3 and confirms the server can be reached. The
    /// future resolves to the connection once HELLO succeeds, or to the error that stopped it.
    /// The rest of `opts` is applied before the connection is returned.
    ///
    /// It then reports the library to the server, `CLIENT SETINFO LIB-NAME componentized:valkey`
    /// and `LIB-VER` with the version of the package, ignoring an error from a server that does
    /// not know CLIENT SETINFO. They survive `reset`, `client-setinfo` replaces them.
    ///
    /// Async so the connection keeps working between commands after it returns, e.g. to read
    /// push messages and to send keep-alive pings.
    @since(version = 0.3.0-dev)
    new: static async func(replies: stream<resp-value>, closed: future<result<_, error-code>>, opts: option<connect-opts>) -> tuple<stream<resp-value>, future<result<connection, error-code>>>;
    /// The server's properties from the reply to HELLO, e.g. `server`, `version` and `mode`.
    @since(version = 0.3.0-dev)
    server-properties: func() -> list<tuple<string, resp-value>>;
    /// Sends a command and waits for its reply.
    ///
    /// Several commands may be in flight at once, replies are paired with commands in the order
    /// the commands were sent.
    ///
    /// For SUBSCRIBE, PSUBSCRIBE, SSUBSCRIBE and their UNSUBSCRIBE counterparts, prefer a
    /// `subscription`, which owns its targets' messages. Once the connection has one, it alone
    /// sends these commands, and they fail with a client error before anything is sent, here,
    /// with `exchange-all`, `emit` and `transaction`.
    ///
    /// RESET, HELLO and CLIENT REPLY always fail with a client error. See `reset` and
    /// `server-properties`, CLIENT REPLY would stop replies from pairing with commands, see
    /// `emit`.
    ///
    /// A command has at least one word, an empty command fails with a client error.
    @since(version = 0.3.0-dev)
    exchange: async func(command: list<string-or-bytes>) -> result<resp-value, error-code>;
    /// Sends the commands one after another, with no other command sent between them, and waits
    /// for every reply.
    ///
    /// Each command has its own result, in order, as if each was sent with `exchange`. Commands
    /// sharing a connection may otherwise be interleaved with other callers' commands, which
    /// matters for a sequence that must not be interrupted, e.g. MULTI, its commands and EXEC.
    @since(version = 0.3.0-dev)
    exchange-all: async func(commands: list<list<string-or-bytes>>) -> list<result<resp-value, error-code>>;
    /// Sends a command without waiting for its reply, resolving once the command is written.
    ///
    /// The server still replies, the reply is discarded when it arrives and an error it reports
    /// is not returned. An implementation may instead ask the server not to reply, e.g. with
    /// `CLIENT REPLY SKIP`.
    ///
    /// A command has at least one word, an empty command fails with a client error.
    @since(version = 0.3.0-dev)
    emit: async func(command: list<string-or-bytes>) -> result<_, error-code>;
    /// Marks the keys to watch, EXEC aborts the next transaction when any of them changed.
    ///
    /// Watched keys belong to the connection, and EXEC, DISCARD or UNWATCH from any caller sharing
    /// it clears them. Use a connection of your own for WATCH, the reads it guards, and the
    /// transaction.
    ///
    /// WATCH key [ key ... ]
    /// https://valkey.io/commands/watch/
    ///
    /// At least one key, no keys fails with a client error.
    @since(version = 0.3.0-dev)
    watch: async func(keys: list<string>) -> result<_, error-code>;
    /// Forgets all the watched keys.
    ///
    /// UNWATCH
    /// https://valkey.io/commands/unwatch/
    @since(version = 0.3.0-dev)
    unwatch: async func() -> result<_, error-code>;
    /// Runs the commands as a transaction, MULTI, each command, then EXEC, with no other command
    /// sent between them, see `exchange-all`.
    ///
    /// Returns none when a watched key changed and the transaction was aborted, otherwise the
    /// reply of each command in order. A command that fails while running is an error value in
    /// its place, the others still ran. A command rejected while queuing, e.g. an unknown
    /// command, fails the transaction with that error, none of the commands ran.
    ///
    /// MULTI and EXEC are only sent as part of a transaction, the commands between them reply
    /// QUEUED rather than their reply.
    ///
    /// MULTI, EXEC
    /// https://valkey.io/commands/multi/
    /// https://valkey.io/commands/exec/
    @since(version = 0.3.0-dev)
    transaction: async func(commands: list<list<string-or-bytes>>) -> result<option<list<resp-value>>, error-code>;
    /// A new subscription, subscribed to nothing yet, see the subscription's `subscribe`.
    ///
    /// From the first subscription, the connection owns its subscriptions: SUBSCRIBE,
    /// PSUBSCRIBE, SSUBSCRIBE and their UNSUBSCRIBE counterparts sent with `exchange` and the like
    /// fail with a client error, so the server's subscriptions stay in step with the
    /// connection's. Subscriptions made with `exchange` before are unsupported. They may keep
    /// delivering to `dead-letters`, the exact behavior is undocumented.
    ///
    /// Fails with a client error when the connection component was built without pub/sub,
    /// e.g. `connection-minimal`. Its push messages are then all dead letters, and SUBSCRIBE and
    /// the like may still be sent with `exchange`.
    @since(version = 0.3.0-dev)
    subscription: func() -> result<subscription, error-code>;
    /// The values nothing else claims, kept apart from replies.
    ///
    /// Push messages that no subscription owns, e.g. client tracking invalidations, messages
    /// and confirmations of SUBSCRIBE sent with `exchange`, or kinds of push messages from a
    /// newer server. Values the server sends unprompted, when no command is waiting for a
    /// reply, are included, e.g. the feed of MONITOR, which are not RESP push values.
    ///
    /// Each call returns a new stream that receives every value arriving after the call,
    /// values arriving while no stream is open are dropped. A stream that falls too far behind
    /// is closed rather than holding up replies, the values it missed are lost.
    @since(version = 0.3.0-dev)
    dead-letters: func() -> stream<resp-value>;
    /// Resets the connection to the state of a new one, then repeats the handshake of `new`.
    ///
    /// RESET discards a transaction in progress, forgets the watched keys, unsubscribes from
    /// every channel, turns off client tracking and the like, selects database 0, and forgets
    /// the client name and the authenticated user. It also switches the connection to RESP2,
    /// so it is followed at once by `HELLO 3` with the `auth` and `client-name` the connection
    /// was established with, with no other command sent between them. `server-properties`
    /// then holds the new reply to HELLO.
    ///
    /// The subscriptions are unsubscribed from every target, each with the reason `reset`, and
    /// stay open for new targets.
    ///
    /// No other command is sent until the handshake succeeds, so none runs on the connection
    /// RESET leaves. When the handshake fails, e.g. the credentials expired since the connection
    /// was established, the connection closes with its error, and the commands queued after
    /// the reset fail with it without being sent. Establish a new connection.
    ///
    /// RESET
    /// https://valkey.io/commands/reset/
    @since(version = 0.3.0-dev)
    reset: async func() -> result<_, error-code>;
    /// Sends PING when no command has been sent and no reply is outstanding for `interval`,
    /// `none` disables it. A PING that fails closes the connection. Disabled by default.
    ///
    /// Unlike TCP keep-alive, a PING counts as activity for the server's idle client timeout,
    /// and works over any transport.
    @since(version = 0.3.0-dev)
    set-keep-alive: func(interval: option<duration-milliseconds>);
  }

  /// The messages of a set of channels, patterns of channels and shard channels, that changes
  /// as targets are subscribed to and unsubscribed from, see `connection.subscription`.
  ///
  /// Subscriptions sharing a connection may overlap, the server stays subscribed to a target while
  /// any subscription has it. The subscription stays open until it is dropped, with or without
  /// targets or streams, then it unsubscribes from its targets. It keeps its connection open.
  @since(version = 0.3.0-dev)
  resource subscription {
    /// Subscribes to the targets, resolving once the server confirms each of them, see
    /// `subscription-event.subscribed`. Targets already subscribed to are left as they are. The
    /// server unsubscribes from a shard channel on its own when its slot moves to another node.
    ///
    /// Each kind of target is subscribed to with its own command, queued with no other command
    /// between them. Every command is sent, even when another fails, and those that succeed
    /// take effect, see `subscription-event.subscribed` and `targets`. The error then holds the
    /// error of each command that failed, e.g. `PSUBSCRIBE: NOPERM ...`. A command is accepted
    /// or refused as a whole, targets of one kind are subscribed to all or none.
    ///
    /// SUBSCRIBE channel [ channel ... ]
    /// PSUBSCRIBE pattern [ pattern ... ]
    /// SSUBSCRIBE shardchannel [ shardchannel ... ]
    /// https://valkey.io/commands/subscribe/
    /// https://valkey.io/commands/psubscribe/
    /// https://valkey.io/commands/ssubscribe/
    ///
    /// At least one target, no targets fails with a client error.
    @since(version = 0.3.0-dev)
    subscribe: async func(targets: list<subscription-target>) -> result<_, error-code>;
    /// The events arriving after the call: targets subscribed to and unsubscribed from, and
    /// messages, in the order they happened.
    ///
    /// Each call returns a new stream, every open stream receives every event. Events arriving
    /// while no stream is open are kept for the next stream, up to 256, later ones are dropped.
    /// Dropping a stream does not unsubscribe, call `events` again for another.
    ///
    /// Pub/sub delivers each message at most once, to all listeners. A stream receives
    /// no events from before it opened, beyond those kept while no stream was open, and does
    /// not start with the current targets, see `targets`. Messages published while nothing
    /// listens are lost, e.g. before a target is subscribed to, after the connection closed, or
    /// past what a lagging stream holds. For delivery that is guaranteed, use a Valkey stream,
    /// see XADD and XREADGROUP.
    ///
    /// The stream closes once the subscription is dropped, the future then resolves to `ok`,
    /// or to the error that closed the connection. A stream that falls too far behind is
    /// closed rather than holding up the connection, its future resolves to `lagged`, and the
    /// events it missed, messages and changes of targets alike, are lost. The future resolves
    /// after the stream delivered what it holds, read the stream to its end first.
    @since(version = 0.3.0-dev)
    events: func() -> tuple<stream<subscription-event>, future<result<_, error-code>>>;
    /// The targets the server confirmed, in the order they were subscribed to, until they are
    /// unsubscribed from.
    ///
    /// Once the connection closes, the targets confirmed until then are still listed, e.g. to
    /// subscribe to them on a new connection. `subscribe` and `unsubscribe` then fail with the
    /// error that closed the connection, and leave the targets as they are.
    @since(version = 0.3.0-dev)
    targets: func() -> list<subscription-target>;
    /// Unsubscribes from the targets, none for every target, resolving once each is
    /// unsubscribed from, see `subscription-event.unsubscribed`. Targets not subscribed to are
    /// left as they are. The server stays subscribed to targets other subscriptions have.
    ///
    /// Each kind of target is unsubscribed from with its own command, as for `subscribe`, e.g.
    /// the `targets` may be given as they are.
    ///
    /// UNSUBSCRIBE [ channel [ channel ... ] ]
    /// PUNSUBSCRIBE [ pattern [ pattern ... ] ]
    /// SUNSUBSCRIBE [ shardchannel [ shardchannel ... ] ]
    /// https://valkey.io/commands/unsubscribe/
    /// https://valkey.io/commands/punsubscribe/
    /// https://valkey.io/commands/sunsubscribe/
    ///
    /// A list of targets has at least one target, otherwise fails with a client error. None
    /// unsubscribes from every target.
    @since(version = 0.3.0-dev)
    unsubscribe: async func(targets: option<list<subscription-target>>) -> result<_, error-code>;
  }
}