diff --git a/.github/workflows/install-shared-dependencies/action.yml b/.github/workflows/install-shared-dependencies/action.yml index af44e7206f..0a134eecc9 100644 --- a/.github/workflows/install-shared-dependencies/action.yml +++ b/.github/workflows/install-shared-dependencies/action.yml @@ -63,7 +63,7 @@ runs: shell: bash if: "${{ inputs.os == 'amazon-linux' }}" run: | - yum install -y gcc pkgconfig openssl openssl-devel which curl gettext --allowerasing + yum install -y gcc pkgconfig openssl openssl-devel which curl gettext tar --allowerasing - name: Install Rust toolchain and protoc if: "${{ !contains(inputs.target, 'musl') }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3403cb5bc9..54312ceb4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,22 @@ #### Changes +* Node: Added XINFO GROUPS command ([#2122](https://github.com/valkey-io/valkey-glide/pull/2122)) +* Java: Added PUBSUB CHANNELS, NUMPAT and NUMSUB commands ([#2105](https://github.com/valkey-io/valkey-glide/pull/2105)) +* Java: Added binary support for custom command ([#2109](https://github.com/valkey-io/valkey-glide/pull/2109)) +* Node: Added SSCAN command ([#2132](https://github.com/valkey-io/valkey-glide/pull/2132)) +* Node: Added HKEYS command ([#2136](https://github.com/aws/glide-for-redis/pull/2136)) +* Node: Added FUNCTION KILL command ([#2114](https://github.com/valkey-io/valkey-glide/pull/2114)) +* Node: Update all commands to use `async` ([#2110](https://github.com/valkey-io/valkey-glide/pull/2110)) +* Node: Added XAUTOCLAIM command ([#2108](https://github.com/valkey-io/valkey-glide/pull/2108)) +* Node: Added XPENDING commands ([#2085](https://github.com/valkey-io/valkey-glide/pull/2085)) +* Node: Added HSCAN command ([#2098](https://github.com/valkey-io/valkey-glide/pull/2098/)) +* Node: Added XINFO CONSUMERS command ([#2093](https://github.com/valkey-io/valkey-glide/pull/2093)) +* Node: Added HRANDFIELD command ([#2096](https://github.com/valkey-io/valkey-glide/pull/2096)) +* Node: Added FUNCTION STATS commands ([#2082](https://github.com/valkey-io/valkey-glide/pull/2082)) +* Node: Added XCLAIM command ([#2092](https://github.com/valkey-io/valkey-glide/pull/2092)) * Node: Added EXPIRETIME and PEXPIRETIME commands ([#2063](https://github.com/valkey-io/valkey-glide/pull/2063)) * Node: Added SORT commands ([#2028](https://github.com/valkey-io/valkey-glide/pull/2028)) * Node: Added LASTSAVE command ([#2059](https://github.com/valkey-io/valkey-glide/pull/2059)) +* Node: Added GEOSEARCHSTORE command ([#2080](https://github.com/valkey-io/valkey-glide/pull/2080)) * Node: Added LCS command ([#2049](https://github.com/valkey-io/valkey-glide/pull/2049)) * Node: Added MSETNX command ([#2046](https://github.com/valkey-io/valkey-glide/pull/2046)) * Node: Added BLMOVE command ([#2027](https://github.com/valkey-io/valkey-glide/pull/2027)) @@ -41,21 +56,32 @@ * Node: Added FUNCTION FLUSH command ([#1984](https://github.com/valkey-io/valkey-glide/pull/1984)) * Node: Added FCALL and FCALL_RO commands ([#2011](https://github.com/valkey-io/valkey-glide/pull/2011)) * Node: Added COPY command ([#2024](https://github.com/valkey-io/valkey-glide/pull/2024)) +* Node: Added MOVE command ([#2104](https://github.com/valkey-io/valkey-glide/pull/2104)) * Node: Added ZMPOP command ([#1994](https://github.com/valkey-io/valkey-glide/pull/1994)) * Node: Added ZINCRBY command ([#2009](https://github.com/valkey-io/valkey-glide/pull/2009)) * Node: Added BZMPOP command ([#2018](https://github.com/valkey-io/valkey-glide/pull/2018)) +* Node: Added XRANGE command ([#2069](https://github.com/valkey-io/valkey-glide/pull/2069)) * Node: Added PFMERGE command ([#2053](https://github.com/valkey-io/valkey-glide/pull/2053)) * Node: Added WATCH and UNWATCH commands ([#2076](https://github.com/valkey-io/valkey-glide/pull/2076)) +* Node: Added WAIT command ([#2113](https://github.com/valkey-io/valkey-glide/pull/2113)) +* Node: Added DUMP and RESTORE commands ([#2126](https://github.com/valkey-io/valkey-glide/pull/2126)) * Node: Added ZLEXCOUNT command ([#2022](https://github.com/valkey-io/valkey-glide/pull/2022)) * Node: Added ZREMRANGEBYLEX command ([#2025](https://github.com/valkey-io/valkey-glide/pull/2025)) +* Node: Added ZRANGESTORE command ([#2068](https://github.com/valkey-io/valkey-glide/pull/2068)) +* Node: Added SRANDMEMBER command ([#2067](https://github.com/valkey-io/valkey-glide/pull/2067)) +* Node: Added XINFO STREAM command ([#2083](https://github.com/valkey-io/valkey-glide/pull/2083)) * Node: Added ZSCAN command ([#2061](https://github.com/valkey-io/valkey-glide/pull/2061)) * Node: Added SETRANGE command ([#2066](https://github.com/valkey-io/valkey-glide/pull/2066)) +* Node: Added APPEND command ([#2095](https://github.com/valkey-io/valkey-glide/pull/2095)) * Node: Added XDEL command ([#2064](https://github.com/valkey-io/valkey-glide/pull/2064)) * Node: Added LMPOP & BLMPOP command ([#2050](https://github.com/valkey-io/valkey-glide/pull/2050)) * Node: Added PUBSUB support ([#1964](https://github.com/valkey-io/valkey-glide/pull/1964)) * Node: Added PUBSUB * commands ([#2090](https://github.com/valkey-io/valkey-glide/pull/2090)) * Python: Added PUBSUB * commands ([#2043](https://github.com/valkey-io/valkey-glide/pull/2043)) * Node: Added XGROUP CREATE & XGROUP DESTROY commands ([#2084](https://github.com/valkey-io/valkey-glide/pull/2084)) +* Node: Added BZPOPMAX & BZPOPMIN command ([#2077]((https://github.com/valkey-io/valkey-glide/pull/2077)) +* Node: Added XGROUP CREATECONSUMER & XGROUP DELCONSUMER commands ([#2088](https://github.com/valkey-io/valkey-glide/pull/2088)) +* Node: Added GETEX command ([#2107]((https://github.com/valkey-io/valkey-glide/pull/2107)) #### Breaking Changes * Node: (Refactor) Convert classes to types ([#2005](https://github.com/valkey-io/valkey-glide/pull/2005)) diff --git a/benchmarks/node/node_benchmark.ts b/benchmarks/node/node_benchmark.ts index 9c7e0b9772..a8ee7be6d8 100644 --- a/benchmarks/node/node_benchmark.ts +++ b/benchmarks/node/node_benchmark.ts @@ -8,7 +8,12 @@ import { parse } from "path"; import percentile from "percentile"; import { RedisClientType, createClient, createCluster } from "redis"; import { stdev } from "stats-lite"; -import { GlideClient, GlideClusterClient, Logger } from "valkey-glide"; +import { + GlideClient, + GlideClusterClient, + GlideString, + Logger, +} from "valkey-glide"; import { generateKeyGet, generateKeySet, @@ -34,8 +39,8 @@ const runningTasks: Promise[] = []; const benchJsonResults: object[] = []; interface IAsyncClient { - set: (key: string, value: string) => Promise; - get: (key: string) => Promise; + set: (key: string, value: string) => Promise; + get: (key: string) => Promise; } function chooseAction(): ChosenAction { diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000000..4c321f286b --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,51 @@ +### Kotlin ### +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### Scala ### +target +*.class + +### Node ### +*.js + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +.bsp + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store diff --git a/examples/java/README.md b/examples/java/README.md index 395ca7d1a7..85d009df47 100644 --- a/examples/java/README.md +++ b/examples/java/README.md @@ -1,6 +1,5 @@ ## Run - -Ensure that you have an instance of Valkey running on "localhost" on "6379". Otherwise, update glide.examples.StandaloneExample or glide.examples.ClusterExample with a configuration that matches your server settings. +Ensure that you have a server running on "localhost" on port "6379". To run the ClusterExample, make sure that the server has cluster mode enabled. If the server is running on a different host and/or port, update the StandaloneExample or ClusterExample with a configuration that matches your server settings. To run the Standalone example: ``` @@ -12,3 +11,11 @@ To run the Cluster example: cd valkey-glide/examples/java ./gradlew :runCluster ``` + +## Version +These examples are running `valkey-glide` version `1.+`. In order to change the version, update the following section in the `build.gradle` file: +```groovy +dependencies { + implementation "io.valkey:valkey-glide:1.+:${osdetector.classifier}" +} +``` diff --git a/examples/kotlin/README.md b/examples/kotlin/README.md new file mode 100644 index 0000000000..a92035aeae --- /dev/null +++ b/examples/kotlin/README.md @@ -0,0 +1,22 @@ +## Run +Ensure that you have a server running on "localhost" on port "6379". To run the ClusterExample, make sure that the server has cluster mode enabled. If the server is running on a different host and/or port, update the StandaloneExample or ClusterExample with a configuration that matches your server settings. + +To run the Standalone example: +```shell +cd valkey-glide/examples/kotlin +./gradlew runStandalone +``` + +To run the Cluster example: +```shell +cd valkey-glide/examples/kotlin +./gradlew runCluster +``` + +## Version +These examples are running `valkey-glide` version `1.+`. In order to change the version, update the following section in the `build.gradle.kts` file: +```kotlin +dependencies { + implementation("io.valkey:valkey-glide:1.+:$classifier") +} +``` diff --git a/examples/kotlin/build.gradle.kts b/examples/kotlin/build.gradle.kts new file mode 100644 index 0000000000..37eee0a263 --- /dev/null +++ b/examples/kotlin/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + kotlin("jvm") version "1.9.23" + application + id("com.google.osdetector") version "1.7.3" +} + +group = "org.example" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +val os = osdetector.os +val classifier = osdetector.classifier +fun nettyTransport(): String { + if (os == "osx") + return "netty-transport-native-kqueue" + else if (os == "linux") + return "netty-transport-native-epoll" + else + throw Exception("Unsupported operating system $os") +} + +dependencies { + testImplementation(kotlin("test")) + implementation("io.valkey:valkey-glide:1.+:$classifier") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") +} + +tasks.test { + useJUnitPlatform() +} + +kotlin { + jvmToolchain(11) +} + +tasks.register("runStandalone") { + group = "application" + description = "Run the standalone example" + classpath = sourceSets.main.get().runtimeClasspath + mainClass = "glide.examples.StandaloneExample" +} + +tasks.register("runCluster") { + group = "application" + description = "Run the cluster example" + classpath = sourceSets.main.get().runtimeClasspath + mainClass = "glide.examples.ClusterExample" +} diff --git a/examples/kotlin/gradle.properties b/examples/kotlin/gradle.properties new file mode 100644 index 0000000000..7fc6f1ff27 --- /dev/null +++ b/examples/kotlin/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/examples/kotlin/gradle/wrapper/gradle-wrapper.jar b/examples/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..249e5832f0 Binary files /dev/null and b/examples/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/kotlin/gradle/wrapper/gradle-wrapper.properties b/examples/kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..f9126bd412 --- /dev/null +++ b/examples/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Jul 09 17:54:28 PDT 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/kotlin/gradlew b/examples/kotlin/gradlew new file mode 100755 index 0000000000..1b6c787337 --- /dev/null +++ b/examples/kotlin/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/kotlin/gradlew.bat b/examples/kotlin/gradlew.bat new file mode 100644 index 0000000000..ac1b06f938 --- /dev/null +++ b/examples/kotlin/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/kotlin/settings.gradle.kts b/examples/kotlin/settings.gradle.kts new file mode 100644 index 0000000000..91009bbe9e --- /dev/null +++ b/examples/kotlin/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} +rootProject.name = "example" + diff --git a/examples/kotlin/src/main/kotlin/ClusterExample.kt b/examples/kotlin/src/main/kotlin/ClusterExample.kt new file mode 100644 index 0000000000..2a0d2c451c --- /dev/null +++ b/examples/kotlin/src/main/kotlin/ClusterExample.kt @@ -0,0 +1,142 @@ +package glide.examples + +import glide.api.GlideClusterClient +import glide.api.logging.Logger +import glide.api.models.commands.InfoOptions +import glide.api.models.configuration.GlideClusterClientConfiguration +import glide.api.models.configuration.NodeAddress +import glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_NODES +import glide.api.models.exceptions.ClosingException +import glide.api.models.exceptions.ConnectionException +import glide.api.models.exceptions.ExecAbortException +import glide.api.models.exceptions.TimeoutException +import java.util.concurrent.CancellationException +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking + +object ClusterExample { + + /** + * Creates and returns a [GlideClusterClient] instance. + * + * This function initializes a [GlideClusterClient] with the provided list of nodes. + * The [nodesList] may contain the address of one or more cluster nodes, and the + * client will automatically discover all nodes in the cluster. + * + * @param nodesList A list of pairs where each pair + * contains a host (String) and port (Int). Defaults to [("localhost", 6379)]. + * + * @return An instance of [GlideClusterClient] connected to the discovered nodes. + */ + private suspend fun createClient(nodesList: List> = listOf(Pair("localhost", 6379))): GlideClusterClient { + // Check `GlideClusterClientConfiguration` for additional options. + val config = GlideClusterClientConfiguration.builder() + .addresses(nodesList.map({ (host: String, port: Int) -> NodeAddress.builder().host(host).port(port).build() })) + .clientName("test_cluster_client") + // Enable this field if the servers are configured with TLS. + //.useTLS(true) + .build() + + return GlideClusterClient.createClient(config).await() + } + + /** + * Executes the main logic of the application, performing basic operations + * such as SET, GET, PING, and INFO REPLICATION using the provided [GlideClusterClient]. + * + * @param client An instance of [GlideClusterClient]. + */ + private suspend fun appLogic(client: GlideClusterClient) { + // Send SET and GET + val setResponse = client.set("foo", "bar").await() + Logger.log(Logger.Level.INFO, "app", "Set response is $setResponse") + + val getResponse = client.get("foo").await() + Logger.log(Logger.Level.INFO, "app", "Get response is $getResponse") + + // Send PING to all primaries (according to Valkey's PING request_policy) + val pong = client.ping().await() + Logger.log(Logger.Level.INFO, "app", "Ping response is $pong") + + // Send INFO REPLICATION with routing option to all nodes + val infoReplResps = client.info( + InfoOptions.builder() + .section(InfoOptions.Section.REPLICATION) + .build(), + ALL_NODES + ).await() + Logger.log( + Logger.Level.INFO, + "app", + "INFO REPLICATION responses from all nodes are=\n$infoReplResps", + ) + } + + /** + * Executes the application logic with exception handling. + */ + private suspend fun execAppLogic() { + while (true) { + var client: GlideClusterClient? = null + try { + client = createClient() + return appLogic(client) + } catch (e: CancellationException) { + Logger.log(Logger.Level.ERROR, "glide", "Request cancelled: ${e.message}") + throw e + } catch (e: Exception) { + when (e) { + is ClosingException -> { + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if (e.message?.contains("NOAUTH") == true) { + Logger.log(Logger.Level.ERROR, "glide", "Authentication error encountered: ${e.message}") + throw e + } else { + Logger.log(Logger.Level.WARN, "glide", "Client has closed and needs to be re-created: ${e.message}") + } + } + is TimeoutException -> { + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log(Logger.Level.ERROR, "glide", "Timeout encountered: ${e.message}") + throw e + } + is ConnectionException -> { + // The client wasn't able to reestablish the connection within the given retries + Logger.log(Logger.Level.ERROR, "glide", "Connection error encountered: ${e.message}") + throw e + } + else -> { + Logger.log(Logger.Level.ERROR, "glide", "Execution error encountered: ${e.cause}") + throw e + } + } + } finally { + try { + client?.close() + } catch (e: Exception) { + Logger.log( + Logger.Level.WARN, + "glide", + "Encountered an error while closing the client: ${e.cause}" + ) + } + } + } + } + + /** + * The entry point of the cluster example. This method sets up the logger configuration + * and executes the main application logic. + * + * @param args Command-line arguments passed to the application. + */ + @JvmStatic + fun main(args: Array) = runBlocking { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(Logger.Level.INFO) + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + execAppLogic() + } +} diff --git a/examples/kotlin/src/main/kotlin/StandaloneExample.kt b/examples/kotlin/src/main/kotlin/StandaloneExample.kt new file mode 100644 index 0000000000..7ca809944c --- /dev/null +++ b/examples/kotlin/src/main/kotlin/StandaloneExample.kt @@ -0,0 +1,127 @@ +package glide.examples + +import glide.api.GlideClient +import glide.api.logging.Logger +import glide.api.models.configuration.GlideClientConfiguration +import glide.api.models.configuration.NodeAddress +import glide.api.models.exceptions.ClosingException +import glide.api.models.exceptions.ConnectionException +import glide.api.models.exceptions.ExecAbortException +import glide.api.models.exceptions.TimeoutException +import java.util.concurrent.CancellationException +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking + +object StandaloneExample { + + /** + * Creates and returns a [GlideClient] instance. + * + * This function initializes a [GlideClient] with the provided list of nodes. + * The [nodesList] may contain either only primary node or a mix of primary + * and replica nodes. The [GlideClient] use these nodes to connect to + * the Standalone setup servers. + * + * @param nodesList A list of pairs where each pair + * contains a host (String) and port (Int). Defaults to [("localhost", 6379)]. + * + * @return An instance of [GlideClient] connected to the specified nodes. + */ + private suspend fun createClient(nodesList: List> = listOf(Pair("localhost", 6379))): GlideClient { + // Check `GlideClientConfiguration` for additional options. + val config = GlideClientConfiguration.builder() + .addresses(nodesList.map({ (host: String, port: Int) -> NodeAddress.builder().host(host).port(port).build() })) + // Enable this field if the servers are configured with TLS. + //.useTLS(true) + .build() + + return GlideClient.createClient(config).await() + } + + /** + * Executes the main logic of the application, performing basic operations such as SET, GET, and + * PING using the provided [GlideClient]. + * + * @param client An instance of [GlideClient]. + */ + private suspend fun appLogic(client: GlideClient) { + // Send SET and GET + val setResponse = client.set("foo", "bar").await() + Logger.log(Logger.Level.INFO, "app", "Set response is $setResponse") + + val getResponse = client.get("foo").await() + Logger.log(Logger.Level.INFO, "app", "Get response is $getResponse") + + // Send PING to the primary node + val pong = client.ping().await() + Logger.log(Logger.Level.INFO, "app", "Ping response is $pong") + } + + /** + * Executes the application logic with exception handling. + */ + private suspend fun execAppLogic() { + while (true) { + var client: GlideClient? = null + try { + client = createClient() + return appLogic(client) + } catch (e: CancellationException) { + Logger.log(Logger.Level.ERROR, "glide", "Request cancelled: ${e.message}") + throw e + } catch (e: Exception) { + when (e) { + is ClosingException -> { + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if (e.message?.contains("NOAUTH") == true) { + Logger.log(Logger.Level.ERROR, "glide", "Authentication error encountered: ${e.message}") + throw e + } else { + Logger.log(Logger.Level.WARN, "glide", "Client has closed and needs to be re-created: ${e.message}") + } + } + is TimeoutException -> { + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log(Logger.Level.ERROR, "glide", "Timeout encountered: ${e.message}") + throw e + } + is ConnectionException -> { + // The client wasn't able to reestablish the connection within the given retries + Logger.log(Logger.Level.ERROR, "glide", "Connection error encountered: ${e.message}") + throw e + } + else -> { + Logger.log(Logger.Level.ERROR, "glide", "Execution error encountered: ${e.cause}") + throw e + } + } + } finally { + try { + client?.close() + } catch (e: Exception) { + Logger.log( + Logger.Level.WARN, + "glide", + "Encountered an error while closing the client: ${e.cause}" + ) + } + } + } + } + + /** + * The entry point of the standalone example. This method sets up the logger configuration + * and executes the main application logic. + * + * @param args Command-line arguments passed to the application. + */ + @JvmStatic + fun main(args: Array) = runBlocking { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(Logger.Level.INFO) + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + execAppLogic() + } +} diff --git a/examples/node/.gitignore b/examples/node/.gitignore deleted file mode 100644 index a6c7c2852d..0000000000 --- a/examples/node/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.js diff --git a/examples/scala/README.md b/examples/scala/README.md new file mode 100644 index 0000000000..fdbfa6b397 --- /dev/null +++ b/examples/scala/README.md @@ -0,0 +1,20 @@ +## Run +Ensure that you have a server running on "localhost" on port "6379". To run the ClusterExample, make sure that the server has cluster mode enabled. If the server is running on a different host and/or port, update the StandaloneExample or ClusterExample with a configuration that matches your server settings. + +To run the Standalone example: +```shell +cd valkey-glide/examples/scala +sbt "runMain StandaloneExample" +``` + +To run the Cluster example: +```shell +cd valkey-glide/examples/scala +sbt "runMain ClusterExample" +``` + +## Version +These examples are running `valkey-glide` version `1.+`. In order to change the version, update the following section in the `build.sbt` file: +```scala +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier platformClassifier +``` diff --git a/examples/scala/build.sbt b/examples/scala/build.sbt new file mode 100644 index 0000000000..811d0e9840 --- /dev/null +++ b/examples/scala/build.sbt @@ -0,0 +1,23 @@ +ThisBuild / version := "0.1.0-SNAPSHOT" + +ThisBuild / scalaVersion := "3.3.3" + +lazy val root = (project in file(".")) + .settings( + name := "example" + ) + +// TODO: Get classifier using https://github.com/phdata/sbt-os-detector if/when https://repository.phdata.io/artifactory/libs-release works again +val os = System.getProperty("os.name").toLowerCase +val platformClassifier = { + (os, System.getProperty("os.arch").toLowerCase) match { + case (mac, arm) if mac.contains("mac") && (arm.contains("aarch") || arm.contains("arm")) => "osx-aarch_64" + case (mac, x86) if mac.contains("mac") && x86.contains("x86") => "osx-x86_64" + case (linux, arm) if linux.contains("linux") && (arm.contains("aarch") || arm.contains("arm")) => "linux-aarch_64" + case (linux, x86) if linux.contains("linux") && x86.contains("x86") => "linux-x86_64" + case (osName, archName) => throw new RuntimeException(s"Unsupported platform $osName $archName") + } +} + +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier platformClassifier + diff --git a/examples/scala/project/build.properties b/examples/scala/project/build.properties new file mode 100644 index 0000000000..136f452e0d --- /dev/null +++ b/examples/scala/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.10.1 diff --git a/examples/scala/src/main/scala/ClusterExample.scala b/examples/scala/src/main/scala/ClusterExample.scala new file mode 100644 index 0000000000..b95b557f9c --- /dev/null +++ b/examples/scala/src/main/scala/ClusterExample.scala @@ -0,0 +1,138 @@ +import glide.api.GlideClusterClient +import glide.api.logging.Logger +import glide.api.models.commands.InfoOptions +import glide.api.models.configuration.{GlideClusterClientConfiguration, NodeAddress} +import glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_NODES +import glide.api.models.exceptions.{ClosingException, ConnectionException, ExecAbortException, TimeoutException} + +import scala.concurrent.{Await, CancellationException, ExecutionContext, Future} +import scala.concurrent.duration.Duration +import scala.concurrent.ExecutionContext.Implicits.global +import scala.jdk.CollectionConverters.* +import scala.jdk.FutureConverters.* +import scala.util.{Failure, Try} + +object ClusterExample { + + /** + * Creates and returns a GlideClusterClient instance. + * + * This function initializes a GlideClusterClient with the provided list of nodes. + * The nodesList may contain the address of one or more cluster nodes, and the + * client will automatically discover all nodes in the cluster. + * + * @param nodesList A list of pairs where each pair + * contains a host (String) and port (Int). Defaults to [("localhost", 6379)]. + * + * @return An instance of Future[GlideClusterClient] connected to the specified nodes. + */ + private def createClient(nodesList: List[(String, Int)] = List(("localhost", 6379))): Future[GlideClusterClient] = { + // Check `GlideClientConfiguration` for additional options. + val config = GlideClusterClientConfiguration.builder() + .addresses(nodesList.map((host, port) => NodeAddress.builder().host(host).port(port).build()).asJava) + // Enable this field if the servers are configured with TLS. + //.useTLS(true) + .build() + // This cast is required in order to pass the config to createClient because the Scala type system + // is unable to resolve the Lombok builder result type. + .asInstanceOf[GlideClusterClientConfiguration] + + GlideClusterClient.createClient(config).asScala + } + + /** + * Executes the main logic of the application, performing basic operations + * such as SET, GET, PING, and INFO REPLICATION using the provided GlideClusterClient. + * + * @param client An instance of GlideClusterClient. + */ + private def appLogic(client: GlideClusterClient): Future[Unit] = { + for { + // Send SET and GET + setResponse <- client.set("foo", "bar").asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Set response is $setResponse") + + getResponse <- client.get("foo").asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Get response is $getResponse") + + // Send PING to all primaries (according to Valkey's PING request_policy) + pong <- client.ping().asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Ping response is $pong") + + // Send INFO REPLICATION with routing option to all nodes + infoReplResps <- client.info( + InfoOptions.builder() + .section(InfoOptions.Section.REPLICATION) + .build(), + ALL_NODES + ).asScala + _ = Logger.log( + Logger.Level.INFO, + "app", + "INFO REPLICATION responses from all nodes are=\n$infoReplResps", + ) + } yield () + } + + /** + * Executes the application logic with exception handling. + */ + private def execAppLogic(): Future[Unit] = { + def loop(): Future[Unit] = { + createClient().flatMap(client => appLogic(client).andThen { + case _ => Try(client.close()) match { + case Failure(e) => + Logger.log( + Logger.Level.WARN, + "glide", + s"Encountered an error while closing the client: ${e.getCause}" + ) + case _ => () + } + }).recoverWith { + case e: CancellationException => + Logger.log(Logger.Level.ERROR, "glide", s"Request cancelled: ${e.getMessage}") + Future.failed(e) + case e: Exception => e.getCause match { + case e: ClosingException if e.getMessage.contains("NOAUTH") => + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + Logger.log(Logger.Level.ERROR, "glide", s"Authentication error encountered: ${e.getMessage}") + Future.failed(e) + case e: ClosingException => + Logger.log(Logger.Level.WARN, "glide", s"Client has closed and needs to be re-created: ${e.getMessage}") + loop() + case e: TimeoutException => + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log(Logger.Level.ERROR, "glide", s"Timeout encountered: ${e.getMessage}") + Future.failed(e) + case e: ConnectionException => + // The client wasn't able to reestablish the connection within the given retries + Logger.log(Logger.Level.ERROR, "glide", s"Connection error encountered: ${e.getMessage}") + Future.failed(e) + case _ => + Logger.log(Logger.Level.ERROR, "glide", s"Execution error encountered: ${e.getCause}") + Future.failed(e) + } + } + } + + loop() + } + + /** + * The entry point of the standalone example. This method sets up the logger configuration + * and executes the main application logic. + * + * @param args Command-line arguments passed to the application. + */ + def main(args: Array[String]): Unit = { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(Logger.Level.INFO) + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + + // Await is used only for this example. Not recommended for use in production environments. + Await.result(execAppLogic(), Duration.Inf) + } +} diff --git a/examples/scala/src/main/scala/StandaloneExample.scala b/examples/scala/src/main/scala/StandaloneExample.scala new file mode 100644 index 0000000000..401a4520c7 --- /dev/null +++ b/examples/scala/src/main/scala/StandaloneExample.scala @@ -0,0 +1,127 @@ +import glide.api.GlideClient +import glide.api.logging.Logger +import glide.api.models.configuration.GlideClientConfiguration +import glide.api.models.configuration.NodeAddress +import glide.api.models.exceptions.{ClosingException, ConnectionException, ExecAbortException, TimeoutException} + +import java.util.concurrent.TimeUnit +import scala.concurrent.{Await, CancellationException, ExecutionContext, Future} +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration.Duration +import scala.jdk.CollectionConverters.* +import scala.jdk.FutureConverters.* +import scala.util.{Failure, Try} + +object StandaloneExample { + + /** + * Creates and returns a GlideClient instance. + * + * This function initializes a GlideClient with the provided list of nodes. + * The nodesLis may contain either only primary node or a mix of primary + * and replica nodes. The GlideClient use these nodes to connect to + * the Standalone setup servers. + * + * @param nodesList A list of pairs where each pair + * contains a host (String) and port (Int). Defaults to [("localhost", 6379)]. + * + * @return An instance of Future[GlideClient] connected to the specified nodes. + */ + private def createClient(nodesList: List[(String, Int)] = List(("localhost", 6379))): Future[GlideClient] = { + // Check `GlideClientConfiguration` for additional options. + val config = GlideClientConfiguration.builder() + .addresses(nodesList.map((host, port) => NodeAddress.builder().host(host).port(port).build()).asJava) + // Enable this field if the servers are configured with TLS. + //.useTLS(true) + .build() + // This cast is required in order to pass the config to createClient because the Scala type system + // is unable to resolve the Lombok builder result type. + .asInstanceOf[GlideClientConfiguration] + + GlideClient.createClient(config).asScala + } + + /** + * Executes the main logic of the application, performing basic operations such as SET, GET, and + * PING using the provided GlideClient. + * + * @param client An instance of GlideClient. + */ + private def appLogic(client: GlideClient): Future[Unit] = { + for { + // Send SET and GET + setResponse <- client.set("foo", "bar").asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Set response is $setResponse") + + getResponse <- client.get("foo").asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Get response is $getResponse") + + // Send PING to the primary node + pong <- client.ping().asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Ping response is $pong") + } yield () + } + + /** + * Executes the application logic with exception handling. + */ + private def execAppLogic(): Future[Unit] = { + def loop(): Future[Unit] = { + createClient().flatMap(client => appLogic(client).andThen { + case _ => Try(client.close()) match { + case Failure(e) => + Logger.log( + Logger.Level.WARN, + "glide", + s"Encountered an error while closing the client: ${e.getCause}" + ) + case _ => () + } + }).recoverWith { + case e: CancellationException => + Logger.log(Logger.Level.ERROR, "glide", s"Request cancelled: ${e.getMessage}") + Future.failed(e) + case e: Exception => e.getCause match { + case e: ClosingException if e.getMessage.contains("NOAUTH") => + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + Logger.log(Logger.Level.ERROR, "glide", s"Authentication error encountered: ${e.getMessage}") + Future.failed(e) + case e: ClosingException => + Logger.log(Logger.Level.WARN, "glide", s"Client has closed and needs to be re-created: ${e.getMessage}") + loop() + case e: TimeoutException => + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log(Logger.Level.ERROR, "glide", s"Timeout encountered: ${e.getMessage}") + Future.failed(e) + case e: ConnectionException => + // The client wasn't able to reestablish the connection within the given retries + Logger.log(Logger.Level.ERROR, "glide", s"Connection error encountered: ${e.getMessage}") + Future.failed(e) + case _ => + Logger.log(Logger.Level.ERROR, "glide", s"Execution error encountered: ${e.getCause}") + Future.failed(e) + } + } + } + + loop() + } + + /** + * The entry point of the standalone example. This method sets up the logger configuration + * and executes the main application logic. + * + * @param args Command-line arguments passed to the application. + */ + def main(args: Array[String]): Unit = { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(Logger.Level.INFO) + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + + val Timeout = 50 + // Change the timeout based on your production environments. + Await.result(execAppLogic(), Duration(Timeout, TimeUnit.SECONDS)) + } +} diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index 3508f83266..baf1a730e1 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -4240,7 +4240,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: core-foundation-sys:0.8.6 +Package: core-foundation-sys:0.8.7 The following copyrights and licenses were found in the source code of this package: @@ -11903,7 +11903,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: js-sys:0.3.69 +Package: js-sys:0.3.70 The following copyrights and licenses were found in the source code of this package: @@ -13582,7 +13582,7 @@ the following restrictions: ---- -Package: mio:1.0.1 +Package: mio:1.0.2 The following copyrights and licenses were found in the source code of this package: @@ -14802,7 +14802,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: object:0.36.2 +Package: object:0.36.3 The following copyrights and licenses were found in the source code of this package: @@ -21689,7 +21689,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde:1.0.204 +Package: serde:1.0.207 The following copyrights and licenses were found in the source code of this package: @@ -21918,7 +21918,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde_derive:1.0.204 +Package: serde_derive:1.0.207 The following copyrights and licenses were found in the source code of this package: @@ -23021,7 +23021,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.72 +Package: syn:2.0.74 The following copyrights and licenses were found in the source code of this package: @@ -27070,7 +27070,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen:0.2.92 +Package: wasm-bindgen:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -27299,7 +27299,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-backend:0.2.92 +Package: wasm-bindgen-backend:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -27528,7 +27528,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro:0.2.92 +Package: wasm-bindgen-macro:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -27757,7 +27757,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro-support:0.2.92 +Package: wasm-bindgen-macro-support:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -27986,7 +27986,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-shared:0.2.92 +Package: wasm-bindgen-shared:0.2.93 The following copyrights and licenses were found in the source code of this package: diff --git a/glide-core/src/client/mod.rs b/glide-core/src/client/mod.rs index 249a409041..4dde234f86 100644 --- a/glide-core/src/client/mod.rs +++ b/glide-core/src/client/mod.rs @@ -23,7 +23,7 @@ mod value_conversion; use tokio::sync::mpsc; pub const HEARTBEAT_SLEEP_DURATION: Duration = Duration::from_secs(1); - +pub const DEFAULT_RETRIES: u32 = 3; pub const DEFAULT_RESPONSE_TIMEOUT: Duration = Duration::from_millis(250); pub const DEFAULT_CONNECTION_ATTEMPT_TIMEOUT: Duration = Duration::from_millis(250); pub const DEFAULT_PERIODIC_CHECKS_INTERVAL: Duration = Duration::from_secs(60); @@ -450,7 +450,8 @@ async fn create_cluster_client( None => Some(DEFAULT_PERIODIC_CHECKS_INTERVAL), }; let mut builder = redis::cluster::ClusterClientBuilder::new(initial_nodes) - .connection_timeout(INTERNAL_CONNECTION_TIMEOUT); + .connection_timeout(INTERNAL_CONNECTION_TIMEOUT) + .retries(DEFAULT_RETRIES); if read_from_replicas { builder = builder.read_from_replicas(); } diff --git a/java/README.md b/java/README.md index d31048b22a..3c1a8cbe1d 100644 --- a/java/README.md +++ b/java/README.md @@ -130,6 +130,22 @@ Maven: ``` +SBT: +- **IMPORTANT** must include a `classifier`. Please use this dependency block and add it to the build.sbt file. +```scala +// osx-aarch_64 +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier "osx-aarch_64" + +// osx-x86_64 +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier "osx-x86_64" + +// linux-aarch_64 +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier "linux-aarch_64" + +// linux-x86_64 +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier "linux-x86_64" +``` + ## Setting up the Java module To use Valkey GLIDE in a Java project with modules, include a module-info.java in your project. @@ -245,6 +261,9 @@ public class Main { } ``` +### Scala and Kotlin Examples +See [our Scala and Kotlin examples](../examples/) to learn how to use Valkey GLIDE in Scala and Kotlin projects. + ### Accessing tests For more examples, you can refer to the test folder [unit tests](./client/src/test/java/glide/api) and [integration tests](./integTest/src/test/java/glide). diff --git a/java/THIRD_PARTY_LICENSES_JAVA b/java/THIRD_PARTY_LICENSES_JAVA index bd1e88cb68..f4fcb68d47 100644 --- a/java/THIRD_PARTY_LICENSES_JAVA +++ b/java/THIRD_PARTY_LICENSES_JAVA @@ -4469,7 +4469,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: core-foundation-sys:0.8.6 +Package: core-foundation-sys:0.8.7 The following copyrights and licenses were found in the source code of this package: @@ -12798,7 +12798,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: js-sys:0.3.69 +Package: js-sys:0.3.70 The following copyrights and licenses were found in the source code of this package: @@ -14477,7 +14477,7 @@ the following restrictions: ---- -Package: mio:1.0.1 +Package: mio:1.0.2 The following copyrights and licenses were found in the source code of this package: @@ -15697,7 +15697,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: object:0.36.2 +Package: object:0.36.3 The following copyrights and licenses were found in the source code of this package: @@ -22584,7 +22584,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde:1.0.204 +Package: serde:1.0.207 The following copyrights and licenses were found in the source code of this package: @@ -22813,7 +22813,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde_derive:1.0.204 +Package: serde_derive:1.0.207 The following copyrights and licenses were found in the source code of this package: @@ -23916,7 +23916,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.72 +Package: syn:2.0.74 The following copyrights and licenses were found in the source code of this package: @@ -27965,7 +27965,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen:0.2.92 +Package: wasm-bindgen:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -28194,7 +28194,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-backend:0.2.92 +Package: wasm-bindgen-backend:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -28423,7 +28423,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro:0.2.92 +Package: wasm-bindgen-macro:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -28652,7 +28652,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro-support:0.2.92 +Package: wasm-bindgen-macro-support:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -28881,7 +28881,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-shared:0.2.92 +Package: wasm-bindgen-shared:0.2.93 The following copyrights and licenses were found in the source code of this package: diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 9c6297f0ad..91182e797e 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -83,6 +83,9 @@ import static command_request.CommandRequestOuterClass.RequestType.PfAdd; import static command_request.CommandRequestOuterClass.RequestType.PfCount; import static command_request.CommandRequestOuterClass.RequestType.PfMerge; +import static command_request.CommandRequestOuterClass.RequestType.PubSubChannels; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumPat; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumSub; import static command_request.CommandRequestOuterClass.RequestType.Publish; import static command_request.CommandRequestOuterClass.RequestType.RPop; import static command_request.CommandRequestOuterClass.RequestType.RPush; @@ -4495,6 +4498,54 @@ public CompletableFuture publish( }); } + @Override + public CompletableFuture pubsubChannels() { + return commandManager.submitNewCommand( + PubSubChannels, + new String[0], + response -> castArray(handleArrayResponse(response), String.class)); + } + + @Override + public CompletableFuture pubsubChannelsBinary() { + return commandManager.submitNewCommand( + PubSubChannels, + new GlideString[0], + response -> castArray(handleArrayResponseBinary(response), GlideString.class)); + } + + @Override + public CompletableFuture pubsubChannels(@NonNull String pattern) { + return commandManager.submitNewCommand( + PubSubChannels, + new String[] {pattern}, + response -> castArray(handleArrayResponse(response), String.class)); + } + + @Override + public CompletableFuture pubsubChannels(@NonNull GlideString pattern) { + return commandManager.submitNewCommand( + PubSubChannels, + new GlideString[] {pattern}, + response -> castArray(handleArrayResponseBinary(response), GlideString.class)); + } + + @Override + public CompletableFuture pubsubNumPat() { + return commandManager.submitNewCommand(PubSubNumPat, new String[0], this::handleLongResponse); + } + + @Override + public CompletableFuture> pubsubNumSub(@NonNull String[] channels) { + return commandManager.submitNewCommand(PubSubNumSub, channels, this::handleMapResponse); + } + + @Override + public CompletableFuture> pubsubNumSub(@NonNull GlideString[] channels) { + return commandManager.submitNewCommand( + PubSubNumSub, channels, this::handleBinaryStringMapResponse); + } + @Override public CompletableFuture watch(@NonNull String[] keys) { return commandManager.submitNewCommand(Watch, keys, this::handleStringResponse); diff --git a/java/client/src/main/java/glide/api/GlideClient.java b/java/client/src/main/java/glide/api/GlideClient.java index 53eaeb369d..ee85b4b393 100644 --- a/java/client/src/main/java/glide/api/GlideClient.java +++ b/java/client/src/main/java/glide/api/GlideClient.java @@ -96,6 +96,12 @@ public CompletableFuture customCommand(@NonNull String[] args) { return commandManager.submitNewCommand(CustomCommand, args, this::handleObjectOrNullResponse); } + @Override + public CompletableFuture customCommand(@NonNull GlideString[] args) { + return commandManager.submitNewCommand( + CustomCommand, args, this::handleBinaryObjectOrNullResponse); + } + @Override public CompletableFuture exec(@NonNull Transaction transaction) { if (transaction.isBinaryOutput()) { diff --git a/java/client/src/main/java/glide/api/GlideClusterClient.java b/java/client/src/main/java/glide/api/GlideClusterClient.java index 65b8721ec8..bd01bce6b1 100644 --- a/java/client/src/main/java/glide/api/GlideClusterClient.java +++ b/java/client/src/main/java/glide/api/GlideClusterClient.java @@ -106,6 +106,15 @@ public CompletableFuture> customCommand(@NonNull String[] a CustomCommand, args, response -> ClusterValue.of(handleObjectOrNullResponse(response))); } + @Override + public CompletableFuture> customCommand(@NonNull GlideString[] args) { + // TODO if a command returns a map as a single value, ClusterValue misleads user + return commandManager.submitNewCommand( + CustomCommand, + args, + response -> ClusterValue.of(handleBinaryObjectOrNullResponse(response))); + } + @Override public CompletableFuture> customCommand( @NonNull String[] args, @NonNull Route route) { @@ -113,6 +122,13 @@ public CompletableFuture> customCommand( CustomCommand, args, route, response -> handleCustomCommandResponse(route, response)); } + @Override + public CompletableFuture> customCommand( + @NonNull GlideString[] args, @NonNull Route route) { + return commandManager.submitNewCommand( + CustomCommand, args, route, response -> handleCustomCommandBinaryResponse(route, response)); + } + protected ClusterValue handleCustomCommandResponse(Route route, Response response) { if (route instanceof SingleNodeRoute) { return ClusterValue.ofSingleValue(handleObjectOrNullResponse(response)); @@ -123,6 +139,16 @@ protected ClusterValue handleCustomCommandResponse(Route route, Response return ClusterValue.ofMultiValue(handleMapResponse(response)); } + protected ClusterValue handleCustomCommandBinaryResponse(Route route, Response response) { + if (route instanceof SingleNodeRoute) { + return ClusterValue.ofSingleValue(handleBinaryObjectOrNullResponse(response)); + } + if (response.hasConstantResponse()) { + return ClusterValue.ofSingleValue(handleStringResponse(response)); + } + return ClusterValue.ofMultiValueBinary(handleBinaryStringMapResponse(response)); + } + @Override public CompletableFuture exec(@NonNull ClusterTransaction transaction) { if (transaction.isBinaryOutput()) { diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index a2d4c92a7d..9eb5bc9a45 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -1153,8 +1153,8 @@ CompletableFuture pexpireAt( * user. * * @see valkey.io for details. - * @param key The key of the set. - * @return The serialized value of a set.
+ * @param key The key to serialize. + * @return The serialized value of the data stored at key.
* If key does not exist, null will be returned. * @example *
{@code
@@ -1171,10 +1171,10 @@ CompletableFuture pexpireAt(
      * deserializing the provided serialized value (obtained via {@link #dump}).
      *
      * @see valkey.io for details.
-     * @param key The key of the set.
+     * @param key The key to create.
      * @param ttl The expiry time (in milliseconds). If 0, the key will
      *     persist.
-     * @param value The serialized value.
+     * @param value The serialized value to deserialize and assign to key.
      * @return Return OK if successfully create a key with a value
      *      .
      * @example
@@ -1189,11 +1189,12 @@ CompletableFuture pexpireAt(
      * Create a key associated with a value that is obtained by
      * deserializing the provided serialized value (obtained via {@link #dump}).
      *
+     * @apiNote IDLETIME and FREQ modifiers cannot be set at the same time.
      * @see valkey.io for details.
-     * @param key The key of the set.
+     * @param key The key to create.
      * @param ttl The expiry time (in milliseconds). If 0, the key will
      *     persist.
-     * @param value The serialized value.
+     * @param value The serialized value to deserialize and assign to key.
      * @param restoreOptions The restore options. See {@link RestoreOptions}.
      * @return Return OK if successfully create a key with a value
      *      .
diff --git a/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java b/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
index 544c40a2c1..dcd0fdd594 100644
--- a/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
+++ b/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
@@ -22,30 +22,45 @@ public interface GenericClusterCommands {
 
     /**
      * Executes a single command, without checking inputs. Every part of the command, including
-     * subcommands, should be added as a separate value in args.
-     *
-     * 

The command will be routed to all primaries. + * subcommands, should be added as a separate value in args.
+ * The command will be routed automatically based on the passed command's default request policy. * - * @apiNote See Valkey * GLIDE Wiki for details on the restrictions and limitations of the custom command API. * @param args Arguments for the custom command including the command name. - * @return The returning value depends on the executed command. + * @return The returned value for the custom command. * @example *

{@code
      * ClusterValue data = client.customCommand(new String[] {"ping"}).get();
-     * assert ((String) data.getSingleValue()).equals("PONG");
+     * assert data.getSingleValue().equals("PONG");
      * }
      */
     CompletableFuture> customCommand(String[] args);
 
     /**
      * Executes a single command, without checking inputs. Every part of the command, including
-     * subcommands, should be added as a separate value in args.
+     * subcommands, should be added as a separate value in args.
+ * The command will be routed automatically based on the passed command's default request policy. * - *

Client will route the command to the nodes defined by route. + * @see Valkey + * GLIDE Wiki for details on the restrictions and limitations of the custom command API. + * @param args Arguments for the custom command including the command name. + * @return The returned value for the custom command. + * @example + *

{@code
+     * ClusterValue data = client.customCommand(new GlideString[] {gs("ping")}).get();
+     * assert data.getSingleValue().equals(gs("PONG"));
+     * }
+     */
+    CompletableFuture> customCommand(GlideString[] args);
+
+    /**
+     * Executes a single command, without checking inputs. Every part of the command, including
+     * subcommands, should be added as a separate value in args.
      *
-     * @apiNote See Valkey
      *     GLIDE Wiki for details on the restrictions and limitations of the custom command API.
      * @param args Arguments for the custom command including the command name
@@ -56,12 +71,33 @@ public interface GenericClusterCommands {
      *     
{@code
      * ClusterValue result = clusterClient.customCommand(new String[]{ "CONFIG", "GET", "maxmemory"}, ALL_NODES).get();
      * Map payload = result.getMultiValue();
-     * assert ((String) payload.get("node1")).equals("1GB");
-     * assert ((String) payload.get("node2")).equals("100MB");
+     * assert payload.get("node1").equals("1GB");
+     * assert payload.get("node2").equals("100MB");
      * }
      */
     CompletableFuture> customCommand(String[] args, Route route);
 
+    /**
+     * Executes a single command, without checking inputs. Every part of the command, including
+     * subcommands, should be added as a separate value in args.
+     *
+     * @see Valkey
+     *     GLIDE Wiki for details on the restrictions and limitations of the custom command API.
+     * @param args Arguments for the custom command including the command name
+     * @param route Specifies the routing configuration for the command. The client will route the
+     *     command to the nodes defined by route.
+     * @return The returning value depends on the executed command and route.
+     * @example
+     *     
{@code
+     * ClusterValue result = clusterClient.customCommand(new GlideString[] { gs("CONFIG"), gs("GET"), gs("maxmemory") }, ALL_NODES).get();
+     * Map payload = result.getMultiValue();
+     * assert payload.get(gs("node1")).equals(gs("1GB"));
+     * assert payload.get(gs("node2")).equals(gs("100MB"));
+     * }
+     */
+    CompletableFuture> customCommand(GlideString[] args, Route route);
+
     /**
      * Executes a transaction by processing the queued commands.
      *
diff --git a/java/client/src/main/java/glide/api/commands/GenericCommands.java b/java/client/src/main/java/glide/api/commands/GenericCommands.java
index a40503a37e..ae72938bad 100644
--- a/java/client/src/main/java/glide/api/commands/GenericCommands.java
+++ b/java/client/src/main/java/glide/api/commands/GenericCommands.java
@@ -22,21 +22,40 @@ public interface GenericCommands {
      * Executes a single command, without checking inputs. Every part of the command, including
      * subcommands, should be added as a separate value in args.
      *
-     * @apiNote See Valkey
      *     GLIDE Wiki for details on the restrictions and limitations of the custom command API.
      * @param args Arguments for the custom command.
-     * @return The returning value depends on the executed command.
+     * @return The returned value for the custom command.
      * @example
      *     
{@code
-     * Object response = (String) client.customCommand(new String[] {"ping", "GLIDE"}).get();
-     * assert ((String) response).equals("GLIDE");
+     * Object response = client.customCommand(new String[] {"ping", "GLIDE"}).get();
+     * assert response.equals("GLIDE");
      * // Get a list of all pub/sub clients:
      * Object result = client.customCommand(new String[]{ "CLIENT", "LIST", "TYPE", "PUBSUB" }).get();
      * }
*/ CompletableFuture customCommand(String[] args); + /** + * Executes a single command, without checking inputs. Every part of the command, including + * subcommands, should be added as a separate value in args. + * + * @see Valkey + * GLIDE Wiki for details on the restrictions and limitations of the custom command API. + * @param args Arguments for the custom command. + * @return The returned value for the custom command. + * @example + *
{@code
+     * Object response = client.customCommand(new GlideString[] {gs("ping"), gs("GLIDE")}).get();
+     * assert response.equals(gs("GLIDE"));
+     * // Get a list of all pub/sub clients:
+     * Object result = client.customCommand(new GlideString[] { gs("CLIENT"), gs("LIST"), gs("TYPE"), gs("PUBSUB") }).get();
+     * }
+ */ + CompletableFuture customCommand(GlideString[] args); + /** * Executes a transaction by processing the queued commands. * diff --git a/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java b/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java index 7abb252747..86ae648faa 100644 --- a/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java @@ -768,7 +768,7 @@ CompletableFuture geosearch( * axis-aligned rectangle, determined by height and width. * * - * @return The number of elements in the resulting set. + * @return The number of elements in the resulting sorted set stored at destination. * @example *
{@code
      * Long result = client
@@ -812,7 +812,7 @@ CompletableFuture geosearchstore(
      *           axis-aligned rectangle, determined by height and width.
      *     
      *
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -861,7 +861,7 @@ CompletableFuture geosearchstore(
      *
      * @param resultOptions Optional inputs for sorting/limiting the results. See - {@link
      *     GeoSearchResultOptions}
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -912,7 +912,7 @@ CompletableFuture geosearchstore(
      *
      * @param resultOptions Optional inputs for sorting/limiting the results. See - {@link
      *     GeoSearchResultOptions}
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -962,7 +962,7 @@ CompletableFuture geosearchstore(
      *     
      *
      * @param options The optional inputs to request additional information.
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -1012,7 +1012,7 @@ CompletableFuture geosearchstore(
      *     
      *
      * @param options The optional inputs to request additional information.
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -1064,7 +1064,7 @@ CompletableFuture geosearchstore(
      * @param options The optional inputs to request additional information.
      * @param resultOptions Optional inputs for sorting/limiting the results. See - {@link
      *     GeoSearchResultOptions}
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -1118,7 +1118,7 @@ CompletableFuture geosearchstore(
      * @param options The optional inputs to request additional information.
      * @param resultOptions Optional inputs for sorting/limiting the results. See - {@link
      *     GeoSearchResultOptions}
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
diff --git a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java
index 5bea78fed9..9140932fc0 100644
--- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java
@@ -591,7 +591,7 @@ public interface HashBaseCommands {
      */
     CompletableFuture hrandfieldWithCountWithValues(String key, long count);
 
-    /*
+    /**
      * Retrieves up to count random field names along with their values from the hash
      * value stored at key.
      *
diff --git a/java/client/src/main/java/glide/api/commands/PubSubBaseCommands.java b/java/client/src/main/java/glide/api/commands/PubSubBaseCommands.java
index 6906d98e06..d83038b3b3 100644
--- a/java/client/src/main/java/glide/api/commands/PubSubBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/PubSubBaseCommands.java
@@ -2,6 +2,7 @@
 package glide.api.commands;
 
 import glide.api.models.GlideString;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -40,4 +41,124 @@ public interface PubSubBaseCommands {
      * }
*/ CompletableFuture publish(GlideString message, GlideString channel); + + /** + * Lists the currently active channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @return An Array of all active channels. + * @example + *
{@code
+     * String[] response = client.pubsubChannels().get();
+     * assert Arrays.equals(new String[] { "channel1", "channel2" });
+     * }
+ */ + CompletableFuture pubsubChannels(); + + /** + * Lists the currently active channels.
+ * Unlike of {@link #pubsubChannels()}, returns channel names as {@link GlideString}s. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @return An Array of all active channels. + * @example + *
{@code
+     * GlideString[] response = client.pubsubChannels().get();
+     * assert Arrays.equals(new GlideString[] { "channel1", "channel2" });
+     * }
+ */ + CompletableFuture pubsubChannelsBinary(); + + /** + * Lists the currently active channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @param pattern A glob-style pattern to match active channels. + * @return An Array of currently active channels matching the given pattern. + * @example + *
{@code
+     * String[] response = client.pubsubChannels("news.*").get();
+     * assert Arrays.equals(new String[] { "news.sports", "news.weather" });
+     * }
+ */ + CompletableFuture pubsubChannels(String pattern); + + /** + * Lists the currently active channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @param pattern A glob-style pattern to match active channels. + * @return An Array of currently active channels matching the given pattern. + * @example + *
{@code
+     * GlideString[] response = client.pubsubChannels(gs("news.*")).get();
+     * assert Arrays.equals(new GlideString[] { gs("news.sports"), gs("news.weather") });
+     * }
+ */ + CompletableFuture pubsubChannels(GlideString pattern); + + /** + * Returns the number of unique patterns that are subscribed to by clients. + * + * @apiNote + *
    + *
  • When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + *
  • This is the total number of unique patterns all the clients are subscribed to, not + * the count of clients subscribed to patterns. + *
+ * + * @see valkey.io for details. + * @return The number of unique patterns. + * @example + *
{@code
+     * Long result = client.pubsubNumPat().get();
+     * assert result == 3L;
+     * }
+ */ + CompletableFuture pubsubNumPat(); + + /** + * Returns the number of subscribers (exclusive of clients subscribed to patterns) for the + * specified channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single map. + * @see valkey.io for details. + * @param channels The list of channels to query for the number of subscribers. + * @return A Map where keys are the channel names and values are the numbers of + * subscribers. + * @example + *
{@code
+     * Map result = client.pubsubNumSub(new String[] {"channel1", "channel2"}).get();
+     * assert result.equals(Map.of("channel1", 3L, "channel2", 5L));
+     * }
+ */ + CompletableFuture> pubsubNumSub(String[] channels); + + /** + * Returns the number of subscribers (exclusive of clients subscribed to patterns) for the + * specified channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single map. + * @see valkey.io for details. + * @param channels The list of channels to query for the number of subscribers. + * @return A Map where keys are the channel names and values are the numbers of + * subscribers. + * @example + *
{@code
+     * Map result = client.pubsubNumSub(new GlideString[] {gs("channel1"), gs("channel2")}).get();
+     * assert result.equals(Map.of(gs("channel1"), 3L, gs("channel2"), 5L));
+     * }
+ */ + CompletableFuture> pubsubNumSub(GlideString[] channels); } diff --git a/java/client/src/main/java/glide/api/commands/StringBaseCommands.java b/java/client/src/main/java/glide/api/commands/StringBaseCommands.java index 38f6c50117..3f46f6a2cb 100644 --- a/java/client/src/main/java/glide/api/commands/StringBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/StringBaseCommands.java @@ -672,8 +672,8 @@ public interface StringBaseCommands { CompletableFuture append(GlideString key, GlideString value); /** - * Returns the longest common subsequence between strings stored at key1 and - * key2. + * Returns all the longest common subsequences combined between strings stored at key1 + * and key2. * * @since Valkey 7.0 and above. * @apiNote When in cluster mode, key1 and key2 must map to the same @@ -681,9 +681,9 @@ public interface StringBaseCommands { * @see valkey.io for details. * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. - * @return A String containing the longest common subsequence between the 2 strings. - * An empty String is returned if the keys do not exist or have no common - * subsequences. + * @return A String containing all the longest common subsequences combined between + * the 2 strings. An empty String is returned if the keys do not exist or have no + * common subsequences. * @example *
{@code
      * // testKey1 = abcd, testKey2 = axcd
@@ -694,8 +694,8 @@ public interface StringBaseCommands {
     CompletableFuture lcs(String key1, String key2);
 
     /**
-     * Returns the longest common subsequence between strings stored at key1 and 
-     * key2.
+     * Returns all the longest common subsequences combined between strings stored at key1
+     *  and key2.
      *
      * @since Valkey 7.0 and above.
      * @apiNote When in cluster mode, key1 and key2 must map to the same
@@ -703,9 +703,9 @@ public interface StringBaseCommands {
      * @see valkey.io for details.
      * @param key1 The key that stores the first string.
      * @param key2 The key that stores the second string.
-     * @return A String containing the longest common subsequence between the 2 strings.
-     *     An empty String is returned if the keys do not exist or have no common
-     *     subsequences.
+     * @return A String containing all the longest common subsequences combined between
+     *     the 2 strings. An empty GlideString is returned if the keys do not exist or
+     *     have no common subsequences.
      * @example
      *     
{@code
      * // testKey1 = abcd, testKey2 = axcd
@@ -716,8 +716,8 @@ public interface StringBaseCommands {
     CompletableFuture lcs(GlideString key1, GlideString key2);
 
     /**
-     * Returns the length of the longest common subsequence between strings stored at key1
-     *  and key2.
+     * Returns the total length of all the longest common subsequences between strings stored at
+     * key1 and key2.
      *
      * @since Valkey 7.0 and above.
      * @apiNote When in cluster mode, key1 and key2 must map to the same
@@ -725,7 +725,7 @@ public interface StringBaseCommands {
      * @see valkey.io for details.
      * @param key1 The key that stores the first string.
      * @param key2 The key that stores the second string.
-     * @return The length of the longest common subsequence between the 2 strings.
+     * @return The total length of all the longest common subsequences the 2 strings.
      * @example
      *     
{@code
      * // testKey1 = abcd, testKey2 = axcd
@@ -736,8 +736,8 @@ public interface StringBaseCommands {
     CompletableFuture lcsLen(String key1, String key2);
 
     /**
-     * Returns the length of the longest common subsequence between strings stored at key1
-     *  and key2.
+     * Returns the total length of all the longest common subsequences between strings stored at
+     * key1 and key2.
      *
      * @since Valkey 7.0 and above.
      * @apiNote When in cluster mode, key1 and key2 must map to the same
@@ -756,8 +756,8 @@ public interface StringBaseCommands {
     CompletableFuture lcsLen(GlideString key1, GlideString key2);
 
     /**
-     * Returns the indices and length of the longest common subsequence between strings stored at
-     * key1 and key2.
+     * Returns the indices and the total length of all the longest common subsequences between strings
+     * stored at key1 and key2.
      *
      * @since Valkey 7.0 and above.
      * @apiNote When in cluster mode, key1 and key2 must map to the same
@@ -766,41 +766,41 @@ public interface StringBaseCommands {
      * @param key1 The key that stores the first string.
      * @param key2 The key that stores the second string.
      * @return A Map containing the indices of the longest common subsequence between the
-     *     2 strings and the length of the longest common subsequence. The resulting map contains two
-     *     keys, "matches" and "len":
+     *     2 strings and the total length of all the longest common subsequences. The resulting map
+     *     contains two keys, "matches" and "len":
      *     
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. *
  • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
- * - * @example If key1 holds the string "abcd123" and key2 - * holds the string "bcdef123" then the sample result would be + * See example for more details. + * @example *
{@code
-     * new Long[][][] {
-     *      {
-     *          {4L, 6L},
-     *          {5L, 7L}
-     *      },
-     *      {
-     *          {1L, 3L},
-     *          {0L, 2L}
-     *      }
-     *  }
+     * client.mset(Map.of("key1", "abcd123", "key2", "bcdef123")).get();
+     * Map response = client.lcsIdx("key1", "key2").get();
+     * // the response contains data in the following format:
+     * Map data = Map.of(
+     *     "matches", new Long[][][] {
+     *         {                         // the first substring match is `"123"`
+     *             {4L, 6L},             // in `"key1"` it is located between indices `4` and `6`
+     *             {5L, 7L}              // and in `"key2"` - in between `5` and `7`
+     *         },
+     *         {                         // second substring match is `"bcd"`
+     *             {1L, 3L},             // in `"key1"` it is located between indices `1` and `3`
+     *             {0L, 2L}              // and in `"key2"` - in between `0` and `2`
+     *         }
+     *     },
+     *     "len", 6                      // total length of the all matches found
+     * );
      * }
- * The result indicates that the first substring match is "123" in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * "bcd" in key1 at index 1 to 3 which matches - * the substring in key2 at index 0 to 2. */ CompletableFuture> lcsIdx(String key1, String key2); /** - * Returns the indices and length of the longest common subsequence between strings stored at - * key1 and key2. + * Returns the indices and the total length of all the longest common subsequences between strings + * stored at key1 and key2. * * @since Valkey 7.0 and above. * @apiNote When in cluster mode, key1 and key2 must map to the same @@ -809,41 +809,41 @@ public interface StringBaseCommands { * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the total length of all the longest common subsequences. The resulting map + * contains two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. *
  • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
- * - * @example If key1 holds the GlideString gs("abcd123") and key2 - * holds the GlideString gs("bcdef123") then the sample result would be + * See example for more details. + * @example *
{@code
-     * new Long[][][] {
-     *      {
-     *          {4L, 6L},
-     *          {5L, 7L}
-     *      },
-     *      {
-     *          {1L, 3L},
-     *          {0L, 2L}
-     *      }
-     *  }
+     * client.mset(Map.of(gs("key1"), gs("abcd123"), gs("key2"), gs("bcdef123"))).get();
+     * Map response = client.lcsIdx(gs("key1"), gs("key2")).get();
+     * // the response contains data in the following format:
+     * Map data = Map.of(
+     *     "matches", new Long[][][] {
+     *         {                         // the first substring match is `gs("123")`
+     *             {4L, 6L},             // in `gs("key1")` it is located between indices `4` and `6`
+     *             {5L, 7L}              // and in `gs("key2")` - in between `5` and `7`
+     *         },
+     *         {                         // second substring match is `gs("bcd")`
+     *             {1L, 3L},             // in `gs("key1")` it is located between indices `1` and `3`
+     *             {0L, 2L}              // and in `gs("key2")` - in between `0` and `2`
+     *         }
+     *     },
+     *     "len", 6                      // total length of the all matches found
+     * );
      * }
- * The result indicates that the first substring match is gs("123") in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * gs("bcd") in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2. */ CompletableFuture> lcsIdx(GlideString key1, GlideString key2); /** - * Returns the indices and length of the longest common subsequence between strings stored at - * key1 and key2. + * Returns the indices and the total length of all the longest common subsequences between strings + * stored at key1 and key2. * * @since Valkey 7.0 and above. * @apiNote When in cluster mode, key1 and key2 must map to the same @@ -853,41 +853,42 @@ public interface StringBaseCommands { * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the total length of all the longest common subsequences. The resulting map + * contains two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. This value doesn't count towards the + * minMatchLen filter. *
  • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
- * - * @example If key1 holds the string "abcd123" and key2 - * holds the string "bcdef123" then the sample result would be + * See example for more details. + * @example *
{@code
-     * new Long[][][] {
-     *      {
-     *          {4L, 6L},
-     *          {5L, 7L}
-     *      },
-     *      {
-     *          {1L, 3L},
-     *          {0L, 2L}
-     *      }
-     *  }
+     * client.mset(Map.of("key1", "abcd123", "key2", "bcdef123")).get();
+     * Map response = client.lcsIdx("key1", "key2", 2).get();
+     * // the response contains data in the following format:
+     * Map data = Map.of(
+     *     "matches", new Long[][][] {
+     *         {                         // the first substring match is `"123"`
+     *             {4L, 6L},             // in `"key1"` it is located between indices `4` and `6`
+     *             {5L, 7L}              // and in `"key2"` - in between `5` and `7`
+     *         },
+     *         {                         // second substring match is `"bcd"`
+     *             {1L, 3L},             // in `"key1"` it is located between indices `1` and `3`
+     *             {0L, 2L}              // and in `"key2"` - in between `0` and `2`
+     *         }
+     *     },
+     *     "len", 6                      // total length of the all matches found
+     * );
      * }
- * The result indicates that the first substring match is "123" in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * "bcd" in key1 at index 1 to 3 which matches - * the substring in key2 at index 0 to 2. */ CompletableFuture> lcsIdx(String key1, String key2, long minMatchLen); /** - * Returns the indices and length of the longest common subsequence between strings stored at - * key1 and key2. + * Returns the indices and the total length of all the longest common subsequences between strings + * stored at key1 and key2. * * @since Valkey 7.0 and above. * @apiNote When in cluster mode, key1 and key2 must map to the same @@ -897,41 +898,42 @@ public interface StringBaseCommands { * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the total length of all the longest common subsequences. The resulting map + * contains two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. This value doesn't count towards the + * minMatchLen filter. *
  • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
- * - * @example If key1 holds the GlideString gs("abcd123") and key2 - * holds the GlideString gs("bcdef123") then the sample result would be + * See example for more details. + * @example *
{@code
-     * new Long[][][] {
-     *      {
-     *          {4L, 6L},
-     *          {5L, 7L}
-     *      },
-     *      {
-     *          {1L, 3L},
-     *          {0L, 2L}
-     *      }
-     *  }
+     * client.mset(Map.of(gs("key1"), gs("abcd123"), gs("key2"), gs("bcdef123"))).get();
+     * Map response = client.lcsIdx(gs("key1"), gs("key2"), 2).get();
+     * // the response contains data in the following format:
+     * Map data = Map.of(
+     *     "matches", new Long[][][] {
+     *         {                         // the first substring match is `gs("123")`
+     *             {4L, 6L},             // in `gs("key1")` it is located between indices `4` and `6`
+     *             {5L, 7L}              // and in `gs("key2")` - in between `5` and `7`
+     *         },
+     *         {                         // second substring match is `gs("bcd")`
+     *             {1L, 3L},             // in `gs("key1")` it is located between indices `1` and `3`
+     *             {0L, 2L}              // and in `gs("key2")` - in between `0` and `2`
+     *         }
+     *     },
+     *     "len", 6                      // total length of the all matches found
+     * );
      * }
- * The result indicates that the first substring match is gs("123") in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * gs("bcd") in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2. */ CompletableFuture> lcsIdx( GlideString key1, GlideString key2, long minMatchLen); /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -941,42 +943,42 @@ CompletableFuture> lcsIdx( * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the lengths of the longest common subsequences. The resulting map contains + * two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
  • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. + *
  • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
- * - * @example If key1 holds the string "abcd1234" and key2 - * holds the string "bcdef1234" then the sample result would be + * See example for more details. + * @example *
{@code
-     * new Object[] {
-     *      new Object[] {
-     *          new Long[] {4L, 7L},
-     *          new Long[] {5L, 8L},
-     *          4L},
-     *      new Object[] {
-     *          new Long[] {1L, 3L},
-     *          new Long[] {0L, 2L},
-     *          3L}
-     *      }
+     * client.mset(Map.of("key1", "abcd1234", "key2", "bcdef1234")).get();
+     * Map response = client.lcsIdxWithMatchLen("key1", "key2").get();
+     * // the response contains data in the following format:
+     * Map data = Map.of(
+     *     "matches", new Object[][] {
+     *         {                                    // the first substring match is `"1234"`
+     *             new Long[] {4L, 7L},             // in `"key1"` it is located between indices `4` and `7`
+     *             new Long[] {5L, 8L},             // and in `"key2"` - in between `5` and `8`
+     *             4L                               // the match length
+     *         },
+     *         {                                    // second substring match is `"bcd"`
+     *             new Long[] {1L, 3L},             // in `"key1"` it is located between indices `1` and `3`
+     *             new Long[] {0L, 2L},             // and in `"key2"` - in between `0` and `2`
+     *             3L                               // the match length
+     *         }
+     *     },
+     *     "len", 6                                 // total length of the all matches found
+     * );
      * }
- * The result indicates that the first substring match is "1234" in key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * "bcd" in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. */ CompletableFuture> lcsIdxWithMatchLen(String key1, String key2); /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -986,43 +988,42 @@ CompletableFuture> lcsIdx( * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the lengths of the longest common subsequences. The resulting map contains + * two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
  • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. + *
  • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
- * - * @example If key1 holds the GlideString gs("abcd1234") and key2 - * holds the GlideString gs("bcdef1234") then the sample result would be + * See example for more details. + * @example *
{@code
-     * new Object[] {
-     *      new Object[] {
-     *          new Long[] {4L, 7L},
-     *          new Long[] {5L, 8L},
-     *          4L},
-     *      new Object[] {
-     *          new Long[] {1L, 3L},
-     *          new Long[] {0L, 2L},
-     *          3L}
-     *      }
+     * client.mset(Map.of(gs("key1"), gs("abcd1234"), gs("key2"), gs("bcdef1234"))).get();
+     * Map response = client.lcsIdxWithMatchLen(gs("key1"), gs("key2")).get();
+     * // the response contains data in the following format:
+     * Map data = Map.of(
+     *     "matches", new Object[][] {
+     *         {                                    // the first substring match is `gs("1234")`
+     *             new Long[] {4L, 7L},             // in `gs("key1")` it is located between indices `4` and `7`
+     *             new Long[] {5L, 8L},             // and in `gs("key2")` - in between `5` and `8`
+     *             4L                               // the match length
+     *         },
+     *         {                                    // second substring match is `"bcd"`
+     *             new Long[] {1L, 3L},             // in `gs("key1")` it is located between indices `1` and `3`
+     *             new Long[] {0L, 2L},             // and in `gs("key2")` - in between `0` and `2`
+     *             3L                               // the match length
+     *         }
+     *     },
+     *     "len", 6                                 // total length of the all matches found
+     * );
      * }
- * The result indicates that the first substring match is gs("1234") in - * key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * gs("bcd") in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. */ CompletableFuture> lcsIdxWithMatchLen(GlideString key1, GlideString key2); /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -1033,43 +1034,44 @@ CompletableFuture> lcsIdx( * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the total length of all the longest common subsequences. The resulting map + * contains two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
  • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. This value doesn't count towards the + * minMatchLen filter. + *
  • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
- * - * @example If key1 holds the string "abcd1234" and key2 - * holds the string "bcdef1234" then the sample result would be + * See example for more details. + * @example *
{@code
-     * new Object[] {
-     *      new Object[] {
-     *          new Long[] {4L, 7L},
-     *          new Long[] {5L, 8L},
-     *          4L},
-     *      new Object[] {
-     *          new Long[] {1L, 3L},
-     *          new Long[] {0L, 2L},
-     *          3L}
-     *      }
+     * client.mset(Map.of("key1", "abcd1234", "key2", "bcdef1234")).get();
+     * Map response = client.lcsIdxWithMatchLen("key1", "key2", 2).get();
+     * // the response contains data in the following format:
+     * Map data = Map.of(
+     *     "matches", new Object[][] {
+     *         {                                    // the first substring match is `"1234"`
+     *             new Long[] {4L, 7L},             // in `"key1"` it is located between indices `4` and `7`
+     *             new Long[] {5L, 8L},             // and in `"key2"` - in between `5` and `8`
+     *             4L                               // the match length
+     *         },
+     *         {                                    // second substring match is `"bcd"`
+     *             new Long[] {1L, 3L},             // in `"key1"` it is located between indices `1` and `3`
+     *             new Long[] {0L, 2L},             // and in `"key2"` - in between `0` and `2`
+     *             3L                               // the match length
+     *         }
+     *     },
+     *     "len", 6                                 // total length of the all matches found
+     * );
      * }
- * The result indicates that the first substring match is "1234" in key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * "bcd" in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. */ CompletableFuture> lcsIdxWithMatchLen( String key1, String key2, long minMatchLen); /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -1080,38 +1082,38 @@ CompletableFuture> lcsIdxWithMatchLen( * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the total length of all the longest common subsequences. The resulting map + * contains two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
  • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. This value doesn't count towards the + * minMatchLen filter. + *
  • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
- * - * @example If key1 holds the GlideString gs("abcd1234") and key2 - * holds the GlideString gs("bcdef1234") then the sample result would be + * See example for more details. + * @example *
{@code
-     * new Object[] {
-     *      new Object[] {
-     *          new Long[] {4L, 7L},
-     *          new Long[] {5L, 8L},
-     *          4L},
-     *      new Object[] {
-     *          new Long[] {1L, 3L},
-     *          new Long[] {0L, 2L},
-     *          3L}
-     *      }
+     * client.mset(Map.of(gs("key1"), gs("abcd1234"), gs("key2"), gs("bcdef1234"))).get();
+     * Map response = client.lcsIdxWithMatchLen(gs("key1"), gs("key2"), 2).get();
+     * // the response contains data in the following format:
+     * Map data = Map.of(
+     *     "matches", new Object[][] {
+     *         {                                    // the first substring match is `gs("1234")`
+     *             new Long[] {4L, 7L},             // in `gs("key1")` it is located between indices `4` and `7`
+     *             new Long[] {5L, 8L},             // and in `gs("key2")` - in between `5` and `8`
+     *             4L                               // the match length
+     *         },
+     *         {                                    // second substring match is `"bcd"`
+     *             new Long[] {1L, 3L},             // in `gs("key1")` it is located between indices `1` and `3`
+     *             new Long[] {0L, 2L},             // and in `gs("key2")` - in between `0` and `2`
+     *             3L                               // the match length
+     *         }
+     *     },
+     *     "len", 6                                 // total length of the all matches found
+     * );
      * }
- * The result indicates that the first substring match is gs("1234") in - * key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * gs("bcd") in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. */ CompletableFuture> lcsIdxWithMatchLen( GlideString key1, GlideString key2, long minMatchLen); diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 6766617426..1130e30f1c 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -105,6 +105,9 @@ import static command_request.CommandRequestOuterClass.RequestType.PfCount; import static command_request.CommandRequestOuterClass.RequestType.PfMerge; import static command_request.CommandRequestOuterClass.RequestType.Ping; +import static command_request.CommandRequestOuterClass.RequestType.PubSubChannels; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumPat; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumSub; import static command_request.CommandRequestOuterClass.RequestType.Publish; import static command_request.CommandRequestOuterClass.RequestType.RPop; import static command_request.CommandRequestOuterClass.RequestType.RPush; @@ -222,6 +225,7 @@ import command_request.CommandRequestOuterClass.Command.ArgsArray; import command_request.CommandRequestOuterClass.RequestType; import command_request.CommandRequestOuterClass.Transaction; +import glide.api.commands.StringBaseCommands; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.FlushMode; import glide.api.models.commands.GetExOptions; @@ -339,7 +343,7 @@ public T withBinaryOutput() { * href="https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command">Glide * Wiki for details on the restrictions and limitations of the custom command API. * @param args Arguments for the custom command. - * @return Command Response - A response from the server with an Object. + * @return Command Response - The returned value for the custom command. */ public T customCommand(ArgType[] args) { checkTypeOrThrow(args); @@ -3035,7 +3039,7 @@ public T zintercard(@NonNull ArgType[] keys, long limit) { * , and stores the result in destination. If destination already * exists, it is overwritten. Otherwise, a new sorted set will be created.
* To perform a zinterstore operation while specifying aggregation settings, use - * {@link #zinterstore(Object, KeysOrWeightedKeys, Aggregate)}. + * {@link #zinterstore(String, KeysOrWeightedKeys, Aggregate)}. * * @see valkey.io for more details. * @param destination The key of the destination sorted set. @@ -3061,7 +3065,7 @@ public T zinterstore( * , and stores the result in destination. If destination already * exists, it is overwritten. Otherwise, a new sorted set will be created.
* To perform a zinterstore operation while specifying aggregation settings, use - * {@link #zinterstore(Object, KeysOrWeightedKeys, Aggregate)}. + * {@link #zinterstore(GlideString, KeysOrWeightedKeysBinary, Aggregate)}. * * @see valkey.io for more details. * @param destination The key of the destination sorted set. @@ -5226,9 +5230,9 @@ public T copy(@NonNull ArgType source, @NonNull ArgType destination) { * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. * @see valkey.io for details. - * @param key The key of the set. - * @return Command Response - The serialized value of a set. If key does not exist, - * null will be returned. + * @param key The key to serialize. + * @return Command Response - The serialized value of the data stored at key.
+ * If key does not exist, null will be returned. */ public T dump(@NonNull ArgType key) { checkTypeOrThrow(key); @@ -5243,12 +5247,12 @@ public T dump(@NonNull ArgType key) { * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. * @see valkey.io for details. - * @param key The key of the set. + * @param key The key to create. * @param ttl The expiry time (in milliseconds). If 0, the key will * persist. - * @param value The serialized value. - * @return Command Response - Return OK if successfully create a key - * with a value. + * @param value The serialized value to deserialize and assign to key. + * @return Command Response - Return OK if the key was successfully + * restored with a value. */ public T restore(@NonNull ArgType key, long ttl, @NonNull byte[] value) { checkTypeOrThrow(key); @@ -5263,14 +5267,15 @@ public T restore(@NonNull ArgType key, long ttl, @NonNull byte[] value * * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. + * @apiNote IDLETIME and FREQ modifiers cannot be set at the same time. * @see valkey.io for details. - * @param key The key of the set. + * @param key The key to create. * @param ttl The expiry time (in milliseconds). If 0, the key will * persist. - * @param value The serialized value. + * @param value The serialized value to deserialize and assign to key. * @param restoreOptions The restore options. See {@link RestoreOptions}. - * @return Command Response - Return OK if successfully create a key - * with a value. + * @return Command Response - Return OK if the key was successfully + * restored with a value. */ public T restore( @NonNull ArgType key, @@ -6240,8 +6245,8 @@ public T functionDelete(@NonNull ArgType libName) { } /** - * Returns the longest common subsequence between strings stored at key1 and - * key2. + * Returns all the longest common subsequences combined between strings stored at key1 + * and key2. * * @since Valkey 7.0 and above. * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type @@ -6249,9 +6254,9 @@ public T functionDelete(@NonNull ArgType libName) { * @see valkey.io for details. * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. - * @return Command Response - A String containing the longest common subsequence - * between the 2 strings. An empty String is returned if the keys do not exist or - * have no common subsequences. + * @return Command Response - A String containing all the longest common subsequences + * combined between the 2 strings. An empty String/GlideString is + * returned if the keys do not exist or have no common subsequences. */ public T lcs(@NonNull ArgType key1, @NonNull ArgType key2) { checkTypeOrThrow(key1); @@ -6260,8 +6265,8 @@ public T lcs(@NonNull ArgType key1, @NonNull ArgType key2) { } /** - * Returns the length of the longest common subsequence between strings stored at key1 - * and key2. + * Returns the total length of all the longest common subsequences between strings stored at + * key1 and key2. * * @since Valkey 7.0 and above. * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type @@ -6269,7 +6274,8 @@ public T lcs(@NonNull ArgType key1, @NonNull ArgType key2) { * @see valkey.io for details. * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. - * @return Command Response - The length of the longest common subsequence between the 2 strings. + * @return Command Response - The total length of all the longest common subsequences between the + * 2 strings. */ public T lcsLen(@NonNull ArgType key1, @NonNull ArgType key2) { checkTypeOrThrow(key1); @@ -6295,6 +6301,75 @@ public T publish(@NonNull ArgType message, @NonNull ArgType channel) { return getThis(); } + /** + * Lists the currently active channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @return Command response - An Array of all active channels. + */ + public T pubsubChannels() { + protobufTransaction.addCommands(buildCommand(PubSubChannels)); + return getThis(); + } + + /** + * Lists the currently active channels. + * + * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type + * will throw {@link IllegalArgumentException}. + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @param pattern A glob-style pattern to match active channels. + * @return Command response - An Array of currently active channels matching the + * given pattern. + */ + public T pubsubChannels(@NonNull ArgType pattern) { + checkTypeOrThrow(pattern); + protobufTransaction.addCommands(buildCommand(PubSubChannels, newArgsBuilder().add(pattern))); + return getThis(); + } + + /** + * Returns the number of unique patterns that are subscribed to by clients. + * + * @apiNote + *
    + *
  • When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + *
  • This is the total number of unique patterns all the clients are subscribed to, not + * the count of clients subscribed to patterns. + *
+ * + * @see valkey.io for details. + * @return Command response - The number of unique patterns. + */ + public T pubsubNumPat() { + protobufTransaction.addCommands(buildCommand(PubSubNumPat)); + return getThis(); + } + + /** + * Returns the number of subscribers (exclusive of clients subscribed to patterns) for the + * specified channels. + * + * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type + * will throw {@link IllegalArgumentException}. + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single map. + * @see valkey.io for details. + * @param channels The list of channels to query for the number of subscribers. + * @return Command response - A Map where keys are the channel names and values are + * the numbers of subscribers. + */ + public T pubsubNumSub(@NonNull ArgType[] channels) { + checkTypeOrThrow(channels); + protobufTransaction.addCommands(buildCommand(PubSubNumSub, newArgsBuilder().add(channels))); + return getThis(); + } + /** * Gets the union of all the given sets. * @@ -6322,35 +6397,16 @@ public T sunion(@NonNull ArgType[] keys) { * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. * @return Command Response - A Map containing the indices of the longest common - * subsequence between the 2 strings and the length of the longest common subsequence. The - * resulting map contains two keys, "matches" and "len": + * subsequence between the 2 strings and the total length of all the longest common + * subsequences. The resulting map contains two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. *
  • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
- * - * @example If key1 holds the string "abcd123" and key2 - * holds the string "bcdef123" then the sample result would be - *
{@code
-     * new Long[][][] {
-     *     {
-     *         {4L, 6L},
-     *         {5L, 7L}
-     *     },
-     *     {
-     *         {1L, 3L},
-     *         {0L, 2L}
-     *     }
-     * }
-     * }
- * The result indicates that the first substring match is "123" in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * "bcd" in key1 at index 1 to 3 which matches - * the substring in key2 at index 0 to 2. + * See example of {@link StringBaseCommands#lcsIdx(String, String)} for more details. */ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2) { checkTypeOrThrow(key1); @@ -6360,8 +6416,8 @@ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2) { } /** - * Returns the indices and length of the longest common subsequence between strings stored at - * key1 and key2. + * Returns the indices and the total length of all the longest common subsequences between strings + * stored at key1 and key2. * * @since Valkey 7.0 and above. * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type @@ -6371,35 +6427,17 @@ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2) { * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return Command Response - A Map containing the indices of the longest common - * subsequence between the 2 strings and the length of the longest common subsequence. The - * resulting map contains two keys, "matches" and "len": + * subsequence between the 2 strings and the total length of all the longest common + * subsequences. The resulting map contains two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. This value doesn't count towards the + * minMatchLen filter. *
  • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
- * - * @example If key1 holds the string "abcd123" and key2 - * holds the string "bcdef123" then the sample result would be - *
{@code
-     * new Long[][][] {
-     *     {
-     *         {4L, 6L},
-     *         {5L, 7L}
-     *     },
-     *     {
-     *         {1L, 3L},
-     *         {0L, 2L}
-     *     }
-     * }
-     * }
- * The result indicates that the first substring match is "123" in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * "bcd" in key1 at index 1 to 3 which matches - * the substring in key2 at index 0 to 2. + * See example of {@link StringBaseCommands#lcsIdx(String, String, long)} for more details. */ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2, long minMatchLen) { checkTypeOrThrow(key1); @@ -6416,7 +6454,7 @@ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2, long min } /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -6426,39 +6464,17 @@ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2, long min * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. * @return Command Response - A Map containing the indices of the longest common - * subsequence between the 2 strings and the length of the longest common subsequence. The + * subsequence between the 2 strings and the lengths of the longest common subsequences. The * resulting map contains two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
  • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. + *
  • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
- * - * @example If key1 holds the string "abcd1234" and key2 - * holds the string "bcdef1234" then the sample result would be - *
{@code
-     * new Object[] {
-     *     new Object[] {
-     *         new Long[] {4L, 7L},
-     *         new Long[] {5L, 8L},
-     *         4L
-     *     },
-     *     new Object[] {
-     *         new Long[] {1L, 3L},
-     *         new Long[] {0L, 2L},
-     *         3L
-     *     }
-     * }
-     * }
- * The result indicates that the first substring match is "1234" in key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * "bcd" in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. + * See example of {@link StringBaseCommands#lcsIdxWithMatchLen(String, String)} for more + * details. */ public T lcsIdxWithMatchLen(@NonNull ArgType key1, @NonNull ArgType key2) { checkTypeOrThrow(key1); @@ -6474,7 +6490,7 @@ public T lcsIdxWithMatchLen(@NonNull ArgType key1, @NonNull ArgType ke } /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -6485,39 +6501,17 @@ public T lcsIdxWithMatchLen(@NonNull ArgType key1, @NonNull ArgType ke * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return Command Response - A Map containing the indices of the longest common - * subsequence between the 2 strings and the length of the longest common subsequence. The + * subsequence between the 2 strings and the lengths of the longest common subsequences. The * resulting map contains two keys, "matches" and "len": *
    - *
  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
  • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. + *
  • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
- * - * @example If key1 holds the string "abcd1234" and key2 - * holds the string "bcdef1234" then the sample result would be - *
{@code
-     * new Object[] {
-     *     new Object[] {
-     *         new Long[] { 4L, 7L },
-     *         new Long[] { 5L, 8L },
-     *         4L
-     *     },
-     *     new Object[] {
-     *         new Long[] { 1L, 3L },
-     *         new Long[] { 0L, 2L },
-     *         3L
-     *     }
-     * }
-     * }
- * The result indicates that the first substring match is "1234" in key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * "bcd" in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. + * See example of {@link StringBaseCommands#lcsIdxWithMatchLen(String, String, long)} for more + * details. */ public T lcsIdxWithMatchLen( @NonNull ArgType key1, @NonNull ArgType key2, long minMatchLen) { diff --git a/java/client/src/main/java/glide/api/models/ClusterValue.java b/java/client/src/main/java/glide/api/models/ClusterValue.java index d141c3cd08..66240a241c 100644 --- a/java/client/src/main/java/glide/api/models/ClusterValue.java +++ b/java/client/src/main/java/glide/api/models/ClusterValue.java @@ -46,13 +46,16 @@ public T getSingleValue() { /** A constructor for the value with type auto-detection. */ @SuppressWarnings("unchecked") public static ClusterValue of(Object data) { - var res = new ClusterValue(); if (data instanceof Map) { - res.multiValue = (Map) data; + var map = (Map) data; + if (map.isEmpty() || map.keySet().toArray()[0] instanceof String) { + return ofMultiValue((Map) data); + } else { // GlideString + return ofMultiValueBinary((Map) data); + } } else { - res.singleValue = (T) data; + return ofSingleValue((T) data); } - return res; } /** A constructor for the value. */ @@ -73,10 +76,9 @@ public static ClusterValue ofMultiValue(Map data) { public static ClusterValue ofMultiValueBinary(Map data) { var res = new ClusterValue(); // the map node address can be converted to a string - Map multiValue = + res.multiValue = data.entrySet().stream() .collect(Collectors.toMap(e -> e.getKey().getString(), Map.Entry::getValue)); - res.multiValue = multiValue; return res; } diff --git a/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java b/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java index 4a069e5a92..a35bd40807 100644 --- a/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java @@ -11,9 +11,10 @@ /** * Optional arguments to {@link GenericBaseCommands#restore(GlideString, long, byte[], - * RestoreOptions)} + * RestoreOptions)}. * * @see valkey.io + * @apiNote IDLETIME and FREQ modifiers cannot be set at the same time. */ @Getter @Builder diff --git a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchStoreOptions.java b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchStoreOptions.java index 047273aaa3..384567b56d 100644 --- a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchStoreOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchStoreOptions.java @@ -15,7 +15,14 @@ public final class GeoSearchStoreOptions { /** Valkey API keyword for {@link #storeDist} parameter. */ public static final String GEOSEARCHSTORE_VALKEY_API = "STOREDIST"; - /** Configure sorting the results with their distance from the center. */ + /** + * Determines what is stored as the sorted set score. Defaults to false.
+ * If set to false, the geohash of the location will be stored as the sorted set + * score.
+ * If set to true, the distance from the center of the shape (circle or box) will be + * stored as the sorted set score. The distance is represented as a floating-point number in the + * same unit specified for that shape. + */ private final boolean storeDist; /** diff --git a/java/client/src/main/java/glide/api/models/configuration/ClusterSubscriptionConfiguration.java b/java/client/src/main/java/glide/api/models/configuration/ClusterSubscriptionConfiguration.java index a29ddd3d83..c45d6abb33 100644 --- a/java/client/src/main/java/glide/api/models/configuration/ClusterSubscriptionConfiguration.java +++ b/java/client/src/main/java/glide/api/models/configuration/ClusterSubscriptionConfiguration.java @@ -1,6 +1,8 @@ /** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.models.configuration; +import static glide.api.models.GlideString.gs; + import glide.api.GlideClusterClient; import glide.api.models.GlideString; import java.util.HashMap; @@ -91,6 +93,17 @@ public ClusterSubscriptionConfigurationBuilder subscription( return this; } + /** + * Add a subscription to a channel or to multiple channels if {@link + * PubSubClusterChannelMode#PATTERN} is used.
+ * See {@link ClusterSubscriptionConfiguration#subscriptions}. + */ + public ClusterSubscriptionConfigurationBuilder subscription( + PubSubClusterChannelMode mode, String channelOrPattern) { + addSubscription(subscriptions, mode, gs(channelOrPattern)); + return this; + } + /** * Set all subscriptions in a bulk. Rewrites previously stored configurations.
* See {@link ClusterSubscriptionConfiguration#subscriptions}. diff --git a/java/client/src/test/java/glide/api/GlideClientTest.java b/java/client/src/test/java/glide/api/GlideClientTest.java index cb29738bbb..50e812a511 100644 --- a/java/client/src/test/java/glide/api/GlideClientTest.java +++ b/java/client/src/test/java/glide/api/GlideClientTest.java @@ -107,6 +107,9 @@ import static command_request.CommandRequestOuterClass.RequestType.PfCount; import static command_request.CommandRequestOuterClass.RequestType.PfMerge; import static command_request.CommandRequestOuterClass.RequestType.Ping; +import static command_request.CommandRequestOuterClass.RequestType.PubSubChannels; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumPat; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumSub; import static command_request.CommandRequestOuterClass.RequestType.Publish; import static command_request.CommandRequestOuterClass.RequestType.RPop; import static command_request.CommandRequestOuterClass.RequestType.RPush; @@ -419,6 +422,30 @@ public void customCommand_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void customCommand_binary_returns_success() { + // setup + GlideString key = gs("testKey"); + Object value = "testValue"; + GlideString cmd = gs("GETSTRING"); + GlideString[] arguments = new GlideString[] {cmd, key}; + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(CustomCommand), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.customCommand(arguments); + String payload = (String) response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void exec() { @@ -13494,6 +13521,170 @@ public void publish_returns_success() { assertEquals(OK, payload); } + @SneakyThrows + @Test + public void pubsubChannels_returns_success() { + // setup + String[] arguments = new String[0]; + String[] value = new String[] {"ch1", "ch2"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PubSubChannels), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pubsubChannels(); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubChannelsBinary_returns_success() { + // setup + GlideString[] arguments = new GlideString[0]; + GlideString[] value = new GlideString[] {gs("ch1"), gs("ch2")}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PubSubChannels), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pubsubChannelsBinary(); + GlideString[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubChannels_with_pattern_returns_success() { + // setup + String pattern = "ch*"; + String[] arguments = new String[] {pattern}; + String[] value = new String[] {"ch1", "ch2"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PubSubChannels), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pubsubChannels(pattern); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubChannelsBinary_with_pattern_returns_success() { + // setup + GlideString pattern = gs("ch*"); + GlideString[] arguments = new GlideString[] {pattern}; + GlideString[] value = new GlideString[] {gs("ch1"), gs("ch2")}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PubSubChannels), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pubsubChannels(pattern); + GlideString[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubNumPat_returns_success() { + // setup + String[] arguments = new String[0]; + Long value = 42L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PubSubNumPat), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pubsubNumPat(); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubNumSub_returns_success() { + // setup + String[] arguments = new String[] {"ch1", "ch2"}; + Map value = Map.of(); + + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.>submitNewCommand(eq(PubSubNumSub), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture> response = service.pubsubNumSub(arguments); + Map payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubNumSubBinary_returns_success() { + // setup + GlideString[] arguments = new GlideString[] {gs("ch1"), gs("ch2")}; + Map value = Map.of(); + + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.>submitNewCommand( + eq(PubSubNumSub), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture> response = service.pubsubNumSub(arguments); + Map payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void sunion_returns_success() { diff --git a/java/client/src/test/java/glide/api/GlideClusterClientTest.java b/java/client/src/test/java/glide/api/GlideClusterClientTest.java index 54b49d9de2..648c64bf33 100644 --- a/java/client/src/test/java/glide/api/GlideClusterClientTest.java +++ b/java/client/src/test/java/glide/api/GlideClusterClientTest.java @@ -120,6 +120,42 @@ public void custom_command_returns_multi_value() { } } + @Test + @SneakyThrows + public void custom_command_binary_returns_single_value() { + var commandManager = new TestCommandManager(null); + + try (var client = new TestClient(commandManager, "TEST")) { + var value = client.customCommand(new GlideString[0]).get(); + assertEquals("TEST", value.getSingleValue()); + } + } + + @Test + @SneakyThrows + public void custom_command_binary_returns_multi_value() { + var commandManager = new TestCommandManager(null); + + var data = Map.of("key1", "value1", "key2", "value2"); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(new GlideString[0]).get(); + assertEquals(data, value.getMultiValue()); + } + } + + @Test + @SneakyThrows + public void custom_command_binary_returns_multi_binary_value() { + var commandManager = new TestCommandManager(null); + + var data = Map.of(gs("key1"), "value1", gs("key2"), "value2"); + var dataNormalized = Map.of("key1", "value1", "key2", "value2"); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(new GlideString[0]).get(); + assertEquals(dataNormalized, value.getMultiValue()); + } + } + @Test @SneakyThrows // test checks that even a map returned as a single value when single node route is used @@ -158,6 +194,45 @@ public void custom_command_returns_single_value_on_constant_response() { } } + @Test + @SneakyThrows + // test checks that even a map returned as a single value when single node route is used + public void custom_command_binary_with_single_node_route_returns_single_value() { + var commandManager = new TestCommandManager(null); + + var data = Map.of("key1", "value1", "key2", "value2"); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(new GlideString[0], RANDOM).get(); + assertEquals(data, value.getSingleValue()); + } + } + + @Test + @SneakyThrows + public void custom_command_binary_with_multi_node_route_returns_multi_value() { + var commandManager = new TestCommandManager(null); + + var data = Map.of(gs("key1"), "value1", gs("key2"), "value2"); + var dataNormalized = Map.of("key1", "value1", "key2", "value2"); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(new GlideString[0], ALL_NODES).get(); + assertEquals(dataNormalized, value.getMultiValue()); + } + } + + @Test + @SneakyThrows + public void custom_command_binary_returns_single_value_on_constant_response() { + var commandManager = + new TestCommandManager( + Response.newBuilder().setConstantResponse(ConstantResponse.OK).build()); + + try (var client = new TestClient(commandManager, "OK")) { + var value = client.customCommand(new GlideString[0], ALL_NODES).get(); + assertEquals("OK", value.getSingleValue()); + } + } + private static class TestClient extends GlideClusterClient { private final Object object; diff --git a/java/client/src/test/java/glide/api/models/ClusterValueTests.java b/java/client/src/test/java/glide/api/models/ClusterValueTests.java index d27bb1aaba..254abd49a4 100644 --- a/java/client/src/test/java/glide/api/models/ClusterValueTests.java +++ b/java/client/src/test/java/glide/api/models/ClusterValueTests.java @@ -41,6 +41,20 @@ public void handle_single_data() { assertThrows(Throwable.class, value::getMultiValue).getMessage())); } + @Test + public void handle_empty_map() { + var value = ClusterValue.of(Map.of()); + assertAll( + () -> assertTrue(value.hasMultiData()), + () -> assertFalse(value.hasSingleData()), + () -> assertNotNull(value.getMultiValue()), + () -> assertEquals(Map.of(), value.getMultiValue()), + () -> + assertEquals( + "No single value stored", + assertThrows(Throwable.class, value::getSingleValue).getMessage())); + } + @Test public void handle_multi_data() { var data = Map.of("node1", Map.of("config1", "param1", "config2", "param2"), "node2", Map.of()); @@ -56,6 +70,32 @@ public void handle_multi_data() { assertThrows(Throwable.class, value::getSingleValue).getMessage())); } + @Test + public void handle_multi_binary_data() { + var data = + Map.of( + gs("node1"), + Map.of(gs("config1"), gs("param1"), gs("config2"), gs("param2")), + gs("node2"), + Map.of()); + var dataNormalized = + Map.of( + "node1", + Map.of(gs("config1"), gs("param1"), gs("config2"), gs("param2")), + "node2", + Map.of()); + var value = ClusterValue.of(data); + assertAll( + () -> assertTrue(value.hasMultiData()), + () -> assertFalse(value.hasSingleData()), + () -> assertNotNull(value.getMultiValue()), + () -> assertEquals(dataNormalized, value.getMultiValue()), + () -> + assertEquals( + "No single value stored", + assertThrows(Throwable.class, value::getSingleValue).getMessage())); + } + @Test public void single_value_ctor() { var value = ClusterValue.ofSingleValue(Map.of("config1", "param1", "config2", "param2")); @@ -87,8 +127,8 @@ public void multi_value_binary_ctor() { () -> assertNotNull(value.getMultiValue()), // ofMultiValueBinary converts the key to a String, but the values are not converted () -> assertTrue(value.getMultiValue().containsKey("config1")), - () -> assertTrue(value.getMultiValue().get("config1").equals(gs("param1"))), + () -> assertEquals(gs("param1"), value.getMultiValue().get("config1")), () -> assertTrue(value.getMultiValue().containsKey("config2")), - () -> assertTrue(value.getMultiValue().get("config2").equals(gs("param2")))); + () -> assertEquals(gs("param2"), value.getMultiValue().get("config2"))); } } diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index aa49a6849b..c112ff7599 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -104,6 +104,9 @@ import static command_request.CommandRequestOuterClass.RequestType.PfCount; import static command_request.CommandRequestOuterClass.RequestType.PfMerge; import static command_request.CommandRequestOuterClass.RequestType.Ping; +import static command_request.CommandRequestOuterClass.RequestType.PubSubChannels; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumPat; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumSub; import static command_request.CommandRequestOuterClass.RequestType.Publish; import static command_request.CommandRequestOuterClass.RequestType.RPop; import static command_request.CommandRequestOuterClass.RequestType.RPush; @@ -1278,6 +1281,18 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), transaction.publish("msg", "ch1"); results.add(Pair.of(Publish, buildArgs("ch1", "msg"))); + transaction.pubsubChannels(); + results.add(Pair.of(PubSubChannels, buildArgs())); + + transaction.pubsubChannels("pattern"); + results.add(Pair.of(PubSubChannels, buildArgs("pattern"))); + + transaction.pubsubNumPat(); + results.add(Pair.of(PubSubNumPat, buildArgs())); + + transaction.pubsubNumSub(new String[] {"ch1", "ch2"}); + results.add(Pair.of(PubSubNumSub, buildArgs("ch1", "ch2"))); + transaction.lcsIdx("key1", "key2"); results.add(Pair.of(LCS, buildArgs("key1", "key2", IDX_COMMAND_STRING))); diff --git a/java/integTest/src/test/java/glide/PubSubTests.java b/java/integTest/src/test/java/glide/PubSubTests.java index 6ca9b3691f..19166cbbf0 100644 --- a/java/integTest/src/test/java/glide/PubSubTests.java +++ b/java/integTest/src/test/java/glide/PubSubTests.java @@ -2,6 +2,7 @@ package glide; import static glide.TestConfiguration.SERVER_VERSION; +import static glide.TestUtilities.assertDeepEquals; import static glide.TestUtilities.commonClientConfig; import static glide.TestUtilities.commonClusterClientConfig; import static glide.api.BaseClient.OK; @@ -27,6 +28,8 @@ import glide.api.models.configuration.BaseSubscriptionConfiguration.MessageCallback; import glide.api.models.configuration.ClusterSubscriptionConfiguration; import glide.api.models.configuration.ClusterSubscriptionConfiguration.PubSubClusterChannelMode; +import glide.api.models.configuration.RequestRoutingConfiguration.SlotKeyRoute; +import glide.api.models.configuration.RequestRoutingConfiguration.SlotType; import glide.api.models.configuration.StandaloneSubscriptionConfiguration; import glide.api.models.configuration.StandaloneSubscriptionConfiguration.PubSubChannelMode; import glide.api.models.exceptions.ConfigurationError; @@ -49,8 +52,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.SneakyThrows; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.tuple.Pair; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -111,10 +115,9 @@ private BaseClient createClientWithSubscriptions( @SneakyThrows private BaseClient createClient(boolean standalone) { - if (standalone) { - return GlideClient.createClient(commonClientConfig().build()).get(); - } - return GlideClusterClient.createClient(commonClusterClientConfig().build()).get(); + return standalone + ? GlideClient.createClient(commonClientConfig().build()).get() + : GlideClusterClient.createClient(commonClusterClientConfig().build()).get(); } /** @@ -128,17 +131,23 @@ private BaseClient createClient(boolean standalone) { private static final int MESSAGE_DELIVERY_DELAY = 500; // ms - @BeforeEach + @AfterEach @SneakyThrows public void cleanup() { for (var client : clients) { if (client instanceof GlideClusterClient) { - ((GlideClusterClient) client).customCommand(new String[] {"unsubscribe"}, ALL_NODES).get(); - ((GlideClusterClient) client).customCommand(new String[] {"punsubscribe"}, ALL_NODES).get(); - ((GlideClusterClient) client).customCommand(new String[] {"sunsubscribe"}, ALL_NODES).get(); + ((GlideClusterClient) client) + .customCommand(new GlideString[] {gs("unsubscribe")}, ALL_NODES) + .get(); + ((GlideClusterClient) client) + .customCommand(new GlideString[] {gs("punsubscribe")}, ALL_NODES) + .get(); + ((GlideClusterClient) client) + .customCommand(new GlideString[] {gs("sunsubscribe")}, ALL_NODES) + .get(); } else { - ((GlideClient) client).customCommand(new String[] {"unsubscribe"}).get(); - ((GlideClient) client).customCommand(new String[] {"punsubscribe"}).get(); + ((GlideClient) client).customCommand(new GlideString[] {gs("unsubscribe")}).get(); + ((GlideClient) client).customCommand(new GlideString[] {gs("punsubscribe")}).get(); } client.close(); } @@ -1232,7 +1241,7 @@ public void pubsub_with_binary(boolean standalone) { createClientWithSubscriptions( standalone, subscriptions, Optional.of(callback), Optional.of(callbackMessages)); var sender = createClient(standalone); - clients.addAll(Arrays.asList(listener, listener2, sender)); + clients.addAll(List.of(listener, listener2, sender)); assertEquals(OK, sender.publish(message.getMessage(), channel).get()); Thread.sleep(MESSAGE_DELIVERY_DELAY); // deliver the messages @@ -1241,4 +1250,222 @@ public void pubsub_with_binary(boolean standalone) { assertEquals(1, callbackMessages.size()); assertEquals(message, callbackMessages.get(0)); } + + @SneakyThrows + @ParameterizedTest(name = "standalone = {0}") + @ValueSource(booleans = {true, false}) + public void pubsub_channels(boolean standalone) { + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); + + // no channels exists yet + var client = createClient(standalone); + assertEquals(0, client.pubsubChannels().get().length); + assertEquals(0, client.pubsubChannelsBinary().get().length); + assertEquals(0, client.pubsubChannels("**").get().length); + assertEquals(0, client.pubsubChannels(gs("**")).get().length); + + var channels = Set.of("test_channel1", "test_channel2", "some_channel"); + String pattern = "test_*"; + + Map> subscriptions = + standalone + ? Map.of( + PubSubChannelMode.EXACT, + channels.stream().map(GlideString::gs).collect(Collectors.toSet())) + : Map.of( + PubSubClusterChannelMode.EXACT, + channels.stream().map(GlideString::gs).collect(Collectors.toSet())); + + var listener = createClientWithSubscriptions(standalone, subscriptions); + clients.addAll(List.of(client, listener)); + + // test without pattern + assertEquals(channels, Set.of(client.pubsubChannels().get())); + assertEquals(channels, Set.of(listener.pubsubChannels().get())); + assertEquals( + channels.stream().map(GlideString::gs).collect(Collectors.toSet()), + Set.of(client.pubsubChannelsBinary().get())); + assertEquals( + channels.stream().map(GlideString::gs).collect(Collectors.toSet()), + Set.of(listener.pubsubChannelsBinary().get())); + + // test with pattern + assertEquals( + Set.of("test_channel1", "test_channel2"), Set.of(client.pubsubChannels(pattern).get())); + assertEquals( + Set.of(gs("test_channel1"), gs("test_channel2")), + Set.of(client.pubsubChannels(gs(pattern)).get())); + assertEquals( + Set.of("test_channel1", "test_channel2"), Set.of(listener.pubsubChannels(pattern).get())); + assertEquals( + Set.of(gs("test_channel1"), gs("test_channel2")), + Set.of(listener.pubsubChannels(gs(pattern)).get())); + + // test with non-matching pattern + assertEquals(0, client.pubsubChannels("non_matching_*").get().length); + assertEquals(0, client.pubsubChannels(gs("non_matching_*")).get().length); + assertEquals(0, listener.pubsubChannels("non_matching_*").get().length); + assertEquals(0, listener.pubsubChannels(gs("non_matching_*")).get().length); + } + + @SneakyThrows + @ParameterizedTest(name = "standalone = {0}") + @ValueSource(booleans = {true, false}) + public void pubsub_numpat(boolean standalone) { + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); + + // no channels exists yet + var client = createClient(standalone); + assertEquals(0, client.pubsubNumPat().get()); + + var patterns = Set.of("news.*", "announcements.*"); + + Map> subscriptions = + standalone + ? Map.of( + PubSubChannelMode.PATTERN, + patterns.stream().map(GlideString::gs).collect(Collectors.toSet())) + : Map.of( + PubSubClusterChannelMode.PATTERN, + patterns.stream().map(GlideString::gs).collect(Collectors.toSet())); + + var listener = createClientWithSubscriptions(standalone, subscriptions); + clients.addAll(List.of(client, listener)); + + assertEquals(2, client.pubsubNumPat().get()); + assertEquals(2, listener.pubsubNumPat().get()); + } + + @SneakyThrows + @ParameterizedTest(name = "standalone = {0}") + @ValueSource(booleans = {true, false}) + public void pubsub_numsub(boolean standalone) { + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); + + // no channels exists yet + var client = createClient(standalone); + var channels = new String[] {"channel1", "channel2", "channel3"}; + assertEquals( + Arrays.stream(channels).collect(Collectors.toMap(c -> c, c -> 0L)), + client.pubsubNumSub(channels).get()); + + Map> subscriptions1 = + standalone + ? Map.of( + PubSubChannelMode.EXACT, Set.of(gs("channel1"), gs("channel2"), gs("channel3"))) + : Map.of( + PubSubClusterChannelMode.EXACT, + Set.of(gs("channel1"), gs("channel2"), gs("channel3"))); + var listener1 = createClientWithSubscriptions(standalone, subscriptions1); + + Map> subscriptions2 = + standalone + ? Map.of(PubSubChannelMode.EXACT, Set.of(gs("channel2"), gs("channel3"))) + : Map.of(PubSubClusterChannelMode.EXACT, Set.of(gs("channel2"), gs("channel3"))); + var listener2 = createClientWithSubscriptions(standalone, subscriptions2); + + Map> subscriptions3 = + standalone + ? Map.of(PubSubChannelMode.EXACT, Set.of(gs("channel3"))) + : Map.of(PubSubClusterChannelMode.EXACT, Set.of(gs("channel3"))); + var listener3 = createClientWithSubscriptions(standalone, subscriptions3); + + Map> subscriptions4 = + standalone + ? Map.of(PubSubChannelMode.PATTERN, Set.of(gs("channel*"))) + : Map.of(PubSubClusterChannelMode.PATTERN, Set.of(gs("channel*"))); + var listener4 = createClientWithSubscriptions(standalone, subscriptions4); + + clients.addAll(List.of(client, listener1, listener2, listener3, listener4)); + + var expected = Map.of("channel1", 1L, "channel2", 2L, "channel3", 3L, "channel4", 0L); + assertEquals(expected, client.pubsubNumSub(ArrayUtils.addFirst(channels, "channel4")).get()); + assertEquals(expected, listener1.pubsubNumSub(ArrayUtils.addFirst(channels, "channel4")).get()); + + var expectedGs = + Map.of(gs("channel1"), 1L, gs("channel2"), 2L, gs("channel3"), 3L, gs("channel4"), 0L); + assertEquals( + expectedGs, + client + .pubsubNumSub( + new GlideString[] {gs("channel1"), gs("channel2"), gs("channel3"), gs("channel4")}) + .get()); + assertEquals( + expectedGs, + listener2 + .pubsubNumSub( + new GlideString[] {gs("channel1"), gs("channel2"), gs("channel3"), gs("channel4")}) + .get()); + } + + @SneakyThrows + @ParameterizedTest(name = "standalone = {0}") + @ValueSource(booleans = {true, false}) + public void pubsub_channels_and_numpat_and_numsub_in_transaction(boolean standalone) { + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); + + var prefix = "{boo}-"; + var route = new SlotKeyRoute(prefix, SlotType.PRIMARY); + var client = createClient(standalone); + var channels = + new String[] {prefix + "test_channel1", prefix + "test_channel2", prefix + "some_channel"}; + var patterns = Set.of(prefix + "news.*", prefix + "announcements.*"); + String pattern = prefix + "test_*"; + + var transaction = + (standalone ? new Transaction() : new ClusterTransaction()) + .pubsubChannels() + .pubsubChannels(pattern) + .pubsubNumPat() + .pubsubNumSub(channels); + // no channels exists yet + var result = + standalone + ? ((GlideClient) client).exec((Transaction) transaction).get() + : ((GlideClusterClient) client).exec((ClusterTransaction) transaction, route).get(); + assertDeepEquals( + new Object[] { + new String[0], // pubsubChannels() + new String[0], // pubsubChannels(pattern) + 0L, // pubsubNumPat() + Arrays.stream(channels) + .collect(Collectors.toMap(c -> c, c -> 0L)), // pubsubNumSub(channels) + }, + result); + + Map> subscriptions = + standalone + ? Map.of( + PubSubChannelMode.EXACT, + Arrays.stream(channels).map(GlideString::gs).collect(Collectors.toSet()), + PubSubChannelMode.PATTERN, + patterns.stream().map(GlideString::gs).collect(Collectors.toSet())) + : Map.of( + PubSubClusterChannelMode.EXACT, + Arrays.stream(channels).map(GlideString::gs).collect(Collectors.toSet()), + PubSubClusterChannelMode.PATTERN, + patterns.stream().map(GlideString::gs).collect(Collectors.toSet())); + + var listener = createClientWithSubscriptions(standalone, subscriptions); + clients.addAll(List.of(client, listener)); + + result = + standalone + ? ((GlideClient) client).exec((Transaction) transaction).get() + : ((GlideClusterClient) client).exec((ClusterTransaction) transaction, route).get(); + + // convert arrays to sets, because we can't compare arrays - they received reordered + result[0] = Set.of((Object[]) result[0]); + result[1] = Set.of((Object[]) result[1]); + + assertDeepEquals( + new Object[] { + Set.of(channels), // pubsubChannels() + Set.of("{boo}-test_channel1", "{boo}-test_channel2"), // pubsubChannels(pattern) + 2L, // pubsubNumPat() + Arrays.stream(channels) + .collect(Collectors.toMap(c -> c, c -> 1L)), // pubsubNumSub(channels) + }, + result); + } } diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index f4256bf4fb..76b367a773 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1438,7 +1438,7 @@ public void hrandfield(BaseClient client) { assertEquals(data.get(pair[0]), pair[1]); } - // Key exists, but it is not a List + // Key exists, but it is not a hash assertEquals(OK, client.set(key2, "value").get()); Exception executionException = assertThrows(ExecutionException.class, () -> client.hrandfield(key2).get()); diff --git a/java/integTest/src/test/java/glide/TestUtilities.java b/java/integTest/src/test/java/glide/TestUtilities.java index 55d5a69d55..97c5222ba7 100644 --- a/java/integTest/src/test/java/glide/TestUtilities.java +++ b/java/integTest/src/test/java/glide/TestUtilities.java @@ -30,10 +30,10 @@ @UtilityClass public class TestUtilities { /** Extract integer parameter value from INFO command output */ - public static int getValueFromInfo(String data, String value) { + public static long getValueFromInfo(String data, String value) { for (var line : data.split("\r\n")) { if (line.contains(value)) { - return Integer.parseInt(line.split(":")[1]); + return Long.parseLong(line.split(":")[1]); } } fail(); @@ -323,8 +323,8 @@ public static GlideString generateLuaLibCodeBinary( } /** - * Create a lua lib with a RO function which runs an endless loop up to timeout sec.
- * Execution takes at least 5 sec regardless of the timeout configured.
+ * Create a lua lib with a function which runs an endless loop up to timeout sec.
+ * Execution takes at least 5 sec regardless of the timeout configured. */ public static String createLuaLibWithLongRunningFunction( String libName, String funcName, int timeout, boolean readOnly) { diff --git a/java/integTest/src/test/java/glide/cluster/CommandTests.java b/java/integTest/src/test/java/glide/cluster/CommandTests.java index 89ca922122..83c6058d26 100644 --- a/java/integTest/src/test/java/glide/cluster/CommandTests.java +++ b/java/integTest/src/test/java/glide/cluster/CommandTests.java @@ -174,6 +174,17 @@ public void custom_command_info() { } } + @Test + @SneakyThrows + public void custom_command_info_binary() { + ClusterValue data = clusterClient.customCommand(new GlideString[] {gs("info")}).get(); + assertTrue(data.hasMultiData()); + for (Object info : data.getMultiValue().values()) { + assertInstanceOf(GlideString.class, info); + assertTrue(info.toString().contains("# Stats")); + } + } + @Test @SneakyThrows public void custom_command_ping() { @@ -181,6 +192,28 @@ public void custom_command_ping() { assertEquals("PONG", data.getSingleValue()); } + @Test + @SneakyThrows + public void custom_command_ping_binary() { + ClusterValue data = clusterClient.customCommand(new GlideString[] {gs("ping")}).get(); + assertEquals(gs("PONG"), data.getSingleValue()); + } + + @Test + @SneakyThrows + public void custom_command_binary_with_route() { + ClusterValue data = + clusterClient.customCommand(new GlideString[] {gs("info")}, ALL_NODES).get(); + for (Object info : data.getMultiValue().values()) { + assertInstanceOf(GlideString.class, info); + assertTrue(info.toString().contains("# Stats")); + } + + data = clusterClient.customCommand(new GlideString[] {gs("info")}, RANDOM).get(); + assertInstanceOf(GlideString.class, data.getSingleValue()); + assertTrue(data.getSingleValue().toString().contains("# Stats")); + } + @Test @SneakyThrows public void custom_command_del_returns_a_number() { @@ -416,14 +449,14 @@ public void clientGetName_with_multi_node_route() { public void config_reset_stat() { var data = clusterClient.info(InfoOptions.builder().section(STATS).build()).get(); String firstNodeInfo = getFirstEntryFromMultiValue(data); - int value_before = getValueFromInfo(firstNodeInfo, "total_net_input_bytes"); + long value_before = getValueFromInfo(firstNodeInfo, "total_net_input_bytes"); var result = clusterClient.configResetStat().get(); assertEquals(OK, result); data = clusterClient.info(InfoOptions.builder().section(STATS).build()).get(); firstNodeInfo = getFirstEntryFromMultiValue(data); - int value_after = getValueFromInfo(firstNodeInfo, "total_net_input_bytes"); + long value_after = getValueFromInfo(firstNodeInfo, "total_net_input_bytes"); assertTrue(value_after < value_before); } diff --git a/java/integTest/src/test/java/glide/standalone/CommandTests.java b/java/integTest/src/test/java/glide/standalone/CommandTests.java index 0a422e52dc..5cdac40b9c 100644 --- a/java/integTest/src/test/java/glide/standalone/CommandTests.java +++ b/java/integTest/src/test/java/glide/standalone/CommandTests.java @@ -105,6 +105,14 @@ public void custom_command_info() { assertTrue(((String) data).contains("# Stats")); } + @Test + @SneakyThrows + public void custom_command_info_binary() { + Object data = regularClient.customCommand(new GlideString[] {gs("info")}).get(); + assertInstanceOf(GlideString.class, data); + assertTrue(data.toString().contains("# Stats")); + } + @Test @SneakyThrows public void custom_command_del_returns_a_number() { @@ -278,13 +286,13 @@ public void clientGetName() { @SneakyThrows public void config_reset_stat() { String data = regularClient.info(InfoOptions.builder().section(STATS).build()).get(); - int value_before = getValueFromInfo(data, "total_net_input_bytes"); + long value_before = getValueFromInfo(data, "total_net_input_bytes"); var result = regularClient.configResetStat().get(); assertEquals(OK, result); data = regularClient.info(InfoOptions.builder().section(STATS).build()).get(); - int value_after = getValueFromInfo(data, "total_net_input_bytes"); + long value_after = getValueFromInfo(data, "total_net_input_bytes"); assertTrue(value_after < value_before); } diff --git a/node/.prettierignore b/node/.prettierignore index 6ced842400..086fea31e1 100644 --- a/node/.prettierignore +++ b/node/.prettierignore @@ -1,6 +1,5 @@ # ignore that dir, because there are a lot of files which we don't manage, e.g. json files in cargo crates rust-client/* -*.md # unignore specific files !rust-client/package.json !rust-client/tsconfig.json diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index 88edc5a8c0..28f95e8a6b 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -4317,7 +4317,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: core-foundation-sys:0.8.6 +Package: core-foundation-sys:0.8.7 The following copyrights and licenses were found in the source code of this package: @@ -12417,7 +12417,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: js-sys:0.3.69 +Package: js-sys:0.3.70 The following copyrights and licenses were found in the source code of this package: @@ -14114,7 +14114,7 @@ the following restrictions: ---- -Package: mio:1.0.1 +Package: mio:1.0.2 The following copyrights and licenses were found in the source code of this package: @@ -14164,7 +14164,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: napi:2.16.8 +Package: napi:2.16.9 The following copyrights and licenses were found in the source code of this package: @@ -14214,7 +14214,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: napi-derive:2.16.10 +Package: napi-derive:2.16.11 The following copyrights and licenses were found in the source code of this package: @@ -14239,7 +14239,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: napi-derive-backend:1.0.72 +Package: napi-derive-backend:1.0.73 The following copyrights and licenses were found in the source code of this package: @@ -15459,7 +15459,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: object:0.36.2 +Package: object:0.36.3 The following copyrights and licenses were found in the source code of this package: @@ -23262,7 +23262,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde:1.0.204 +Package: serde:1.0.207 The following copyrights and licenses were found in the source code of this package: @@ -23491,7 +23491,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde_derive:1.0.204 +Package: serde_derive:1.0.207 The following copyrights and licenses were found in the source code of this package: @@ -24594,7 +24594,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.72 +Package: syn:2.0.74 The following copyrights and licenses were found in the source code of this package: @@ -29330,7 +29330,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen:0.2.92 +Package: wasm-bindgen:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -29559,7 +29559,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-backend:0.2.92 +Package: wasm-bindgen-backend:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -29788,7 +29788,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro:0.2.92 +Package: wasm-bindgen-macro:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -30017,7 +30017,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro-support:0.2.92 +Package: wasm-bindgen-macro-support:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -30246,7 +30246,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-shared:0.2.92 +Package: wasm-bindgen-shared:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -37903,7 +37903,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: @types:node:22.1.0 +Package: @types:node:22.2.0 The following copyrights and licenses were found in the source code of this package: diff --git a/node/npm/glide/index.ts b/node/npm/glide/index.ts index b92c6dd01e..ecf8bec0ba 100644 --- a/node/npm/glide/index.ts +++ b/node/npm/glide/index.ts @@ -104,12 +104,16 @@ function initialize() { GlideClient, GlideClusterClient, GlideClientConfiguration, + GlideString, FunctionListOptions, FunctionListResponse, + FunctionStatsResponse, SlotIdTypes, SlotKeyTypes, + TimeUnit, RouteByAddress, Routes, + RestoreOptions, SingleNodeRoute, PeriodicChecksManualInterval, PeriodicChecks, @@ -122,8 +126,8 @@ function initialize() { InsertPosition, SetOptions, ZaddOptions, - InfScoreBoundary, - ScoreBoundary, + InfBoundary, + Boundary, UpdateOptions, ProtocolVersion, RangeByIndex, @@ -138,12 +142,16 @@ function initialize() { StreamTrimOptions, StreamAddOptions, StreamReadOptions, + StreamClaimOptions, + StreamPendingOptions, ScriptOptions, ClosingError, ConfigurationError, ExecAbortError, RedisError, ReturnType, + StreamEntries, + ReturnTypeXinfoStream, RequestError, TimeoutError, ConnectionError, @@ -179,6 +187,7 @@ function initialize() { BitwiseOperation, ConditionalChange, GeoAddOptions, + GlideString, CoordOrigin, MemberOrigin, SearchOrigin, @@ -194,10 +203,15 @@ function initialize() { GlideClientConfiguration, FunctionListOptions, FunctionListResponse, + FunctionStatsResponse, SlotIdTypes, SlotKeyTypes, + StreamEntries, + TimeUnit, + ReturnTypeXinfoStream, RouteByAddress, Routes, + RestoreOptions, SingleNodeRoute, PeriodicChecksManualInterval, PeriodicChecks, @@ -210,8 +224,8 @@ function initialize() { InsertPosition, SetOptions, ZaddOptions, - InfScoreBoundary, - ScoreBoundary, + InfBoundary, + Boundary, UpdateOptions, ProtocolVersion, RangeByIndex, @@ -225,7 +239,9 @@ function initialize() { StreamGroupOptions, StreamTrimOptions, StreamAddOptions, + StreamClaimOptions, StreamReadOptions, + StreamPendingOptions, ScriptOptions, ClosingError, ConfigurationError, diff --git a/node/rust-client/src/lib.rs b/node/rust-client/src/lib.rs index c301e082de..896478f76a 100644 --- a/node/rust-client/src/lib.rs +++ b/node/rust-client/src/lib.rs @@ -76,7 +76,7 @@ impl AsyncClient { }) } - #[napi(ts_return_type = "Promise")] + #[napi(ts_return_type = "Promise")] #[allow(dead_code)] pub fn get(&self, env: Env, key: String) -> Result { let (deferred, promise) = env.create_deferred()?; @@ -93,7 +93,7 @@ impl AsyncClient { Ok(promise) } - #[napi(ts_return_type = "Promise")] + #[napi(ts_return_type = "Promise")] #[allow(dead_code)] pub fn set(&self, env: Env, key: String, value: String) -> Result { let (deferred, promise) = env.create_deferred()?; @@ -263,7 +263,7 @@ fn redis_value_to_js(val: Value, js_env: Env, string_decoder: bool) -> Result { + const { decoder = this.defaultDecoder, route } = options; + const stringDecoder = decoder === Decoder.String ? true : false; + if (this.isClosed) { throw new ClosingError( "Unable to execute requests; the client is closed. Please create a new client.", @@ -566,7 +657,28 @@ export class BaseClient { return new Promise((resolve, reject) => { const callbackIndex = this.getCallbackIndex(); - this.promiseCallbackFunctions[callbackIndex] = [resolve, reject]; + this.promiseCallbackFunctions[callbackIndex] = [ + (resolveAns: T) => { + if (resolveAns instanceof PointerResponse) { + if (typeof resolveAns === "number") { + resolveAns = valueFromSplitPointer( + 0, + resolveAns, + stringDecoder, + ) as T; + } else { + resolveAns = valueFromSplitPointer( + resolveAns.high!, + resolveAns.low!, + stringDecoder, + ) as T; + } + } + + resolve(resolveAns); + }, + reject, + ]; this.writeOrBufferCommandRequest(callbackIndex, command, route); }); } @@ -668,7 +780,7 @@ export class BaseClient { return [null, null]; } - public getPubSubMessage(): Promise { + public async getPubSubMessage(): Promise { if (this.isClosed) { throw new ClosingError( "Unable to execute requests; the client is closed. Please create a new client.", @@ -693,7 +805,7 @@ export class BaseClient { }); } - public tryGetPubSubMessage(): PubSubMsg | null { + public tryGetPubSubMessage(decoder?: Decoder): PubSubMsg | null { if (this.isClosed) { throw new ClosingError( "Unable to execute requests; the client is closed. Please create a new client.", @@ -717,13 +829,17 @@ export class BaseClient { while (this.pendingPushNotification.length > 0 && !msg) { const pushNotification = this.pendingPushNotification.shift()!; - msg = this.notificationToPubSubMessageSafe(pushNotification); + msg = this.notificationToPubSubMessageSafe( + pushNotification, + decoder, + ); } return msg; } notificationToPubSubMessageSafe( pushNotification: response.Response, + decoder?: Decoder, ): PubSubMsg | null { let msg: PubSubMsg | null = null; const responsePointer = pushNotification.respPointer; @@ -734,15 +850,13 @@ export class BaseClient { nextPushNotificationValue = valueFromSplitPointer( responsePointer.high, responsePointer.low, - // TODO: change according to https://github.com/valkey-io/valkey-glide/pull/2052 - true, + decoder === Decoder.String, ) as Record; } else { nextPushNotificationValue = valueFromSplitPointer( 0, responsePointer, - // TODO: change according to https://github.com/valkey-io/valkey-glide/pull/2052 - true, + decoder === Decoder.String, ) as Record; } @@ -811,28 +925,62 @@ export class BaseClient { } /** Get the value associated with the given key, or null if no such value exists. - * See https://valkey.io/commands/get/ for details. + * + * @see {@link https://valkey.io/commands/get/|valkey.io} for details. * * @param key - The key to retrieve from the database. - * @returns If `key` exists, returns the value of `key` as a string. Otherwise, return null. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. If not set, the default decoder from the client config will be used. + * @returns If `key` exists, returns the value of `key`. Otherwise, return null. * * @example * ```typescript * // Example usage of get method to retrieve the value of a key * const result = await client.get("key"); * console.log(result); // Output: 'value' + * // Example usage of get method to retrieve the value of a key with Bytes decoder + * const result = await client.get("key", Decoder.Bytes); + * console.log(result); // Output: {"data": [118, 97, 108, 117, 101], "type": "Buffer"} + * ``` + */ + public async get( + key: GlideString, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createGet(key), { decoder: decoder }); + } + + /** + * Get the value of `key` and optionally set its expiration. `GETEX` is similar to {@link get}. + * + * @see {@link https://valkey.io/commands/getex/|valkey.op} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key to retrieve from the database. + * @param options - (Optional) Set expiriation to the given key. + * "persist" will retain the time to live associated with the key. Equivalent to `PERSIST` in the VALKEY API. + * Otherwise, a {@link TimeUnit} and duration of the expire time should be specified. + * @returns If `key` exists, returns the value of `key` as a `string`. Otherwise, return `null`. + * + * @example + * ```typescript + * const result = await client.getex("key", {expiry: { type: TimeUnit.Seconds, count: 5 }}); + * console.log(result); // Output: 'value' * ``` */ - public get(key: string): Promise { - return this.createWritePromise(createGet(key)); + public async getex( + key: string, + options?: "persist" | { type: TimeUnit; duration: number }, + ): Promise { + return this.createWritePromise(createGetEx(key, options)); } /** * Gets a string value associated with the given `key`and deletes the key. * - * See https://valkey.io/commands/getdel/ for details. + * @see {@link https://valkey.io/commands/getdel/|valkey.io} for details. * * @param key - The key to retrieve from the database. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. If not set, the default decoder from the client config will be used. * @returns If `key` exists, returns the `value` of `key`. Otherwise, return `null`. * * @example @@ -843,8 +991,11 @@ export class BaseClient { * const value = client.getdel("key"); // value is null * ``` */ - public getdel(key: string): Promise { - return this.createWritePromise(createGetDel(key)); + public async getdel( + key: GlideString, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createGetDel(key), { decoder: decoder }); } /** @@ -854,11 +1005,12 @@ export class BaseClient { * penultimate and so forth. If `key` does not exist, an empty string is returned. If `start` * or `end` are out of range, returns the substring within the valid range of the string. * - * See https://valkey.io/commands/getrange/ for details. + * @see {@link https://valkey.io/commands/getrange/|valkey.io} for details. * * @param key - The key of the string. * @param start - The starting offset. * @param end - The ending offset. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. If not set, the default decoder from the client config will be used. * @returns A substring extracted from the value stored at `key`. * * @example @@ -875,15 +1027,19 @@ export class BaseClient { * ``` */ public async getrange( - key: string, + key: GlideString, start: number, end: number, - ): Promise { - return this.createWritePromise(createGetRange(key, start, end)); + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createGetRange(key, start, end), { + decoder: decoder, + }); } /** Set the given key with the given value. Return value is dependent on the passed options. - * See https://valkey.io/commands/set/ for details. + * + * @see {@link https://valkey.io/commands/set/|valkey.io} for details. * * @param key - The key to store. * @param value - The value to store with the given key. @@ -899,7 +1055,7 @@ export class BaseClient { * console.log(result); // Output: 'OK' * * // Example usage of set method with conditional options and expiration - * const result2 = await client.set("key", "new_value", {conditionalSet: "onlyIfExists", expiry: { type: "seconds", count: 5 }}); + * const result2 = await client.set("key", "new_value", {conditionalSet: "onlyIfExists", expiry: { type: TimeUnit.Seconds, count: 5 }}); * console.log(result2); // Output: 'OK' - Set "new_value" to "key" only if "key" already exists, and set the key expiration to 5 seconds. * * // Example usage of set method with conditional options and returning old value @@ -911,7 +1067,7 @@ export class BaseClient { * console.log(result4); // Output: 'new_value' - Value wasn't modified back to being "value" because of "NX" flag. * ``` */ - public set( + public async set( key: string | Uint8Array, value: string | Uint8Array, options?: SetOptions, @@ -920,7 +1076,8 @@ export class BaseClient { } /** Removes the specified keys. A key is ignored if it does not exist. - * See https://valkey.io/commands/del/ for details. + * + * @see {@link https://valkey.io/commands/del/|valkey.io} for details. * * @param keys - the keys we wanted to remove. * @returns the number of keys that were removed. @@ -940,15 +1097,92 @@ export class BaseClient { * console.log(result); // Output: 0 * ``` */ - public del(keys: string[]): Promise { + public async del(keys: string[]): Promise { return this.createWritePromise(createDel(keys)); } + /** + * Serialize the value stored at `key` in a Valkey-specific format and return it to the user. + * + * @See {@link https://valkey.io/commands/dump/|valkey.io} for details. + * + * @param key - The `key` to serialize. + * @returns The serialized value of the data stored at `key`. If `key` does not exist, `null` will be returned. + * + * @example + * ```typescript + * let result = await client.dump("myKey"); + * console.log(result); // Output: the serialized value of "myKey" + * ``` + * + * @example + * ```typescript + * result = await client.dump("nonExistingKey"); + * console.log(result); // Output: `null` + * ``` + */ + public async dump(key: GlideString): Promise { + return this.createWritePromise(createDump(key), { + decoder: Decoder.Bytes, + }); + } + + /** + * Create a `key` associated with a `value` that is obtained by deserializing the provided + * serialized `value` (obtained via {@link dump}). + * + * @See {@link https://valkey.io/commands/restore/|valkey.io} for details. + * @remarks `options.idletime` and `options.frequency` modifiers cannot be set at the same time. + * + * @param key - The `key` to create. + * @param ttl - The expiry time (in milliseconds). If `0`, the `key` will persist. + * @param value - The serialized value to deserialize and assign to `key`. + * @param options - (Optional) Restore options {@link RestoreOptions}. + * @returns Return "OK" if the `key` was successfully restored with a `value`. + * + * @example + * ```typescript + * const result = await client.restore("myKey", 0, value); + * console.log(result); // Output: "OK" + * ``` + * + * @example + * ```typescript + * const result = await client.restore("myKey", 1000, value, {replace: true, absttl: true}); + * console.log(result); // Output: "OK" + * ``` + * + * @example + * ```typescript + * const result = await client.restore("myKey", 0, value, {replace: true, idletime: 10}); + * console.log(result); // Output: "OK" + * ``` + * + * @example + * ```typescript + * const result = await client.restore("myKey", 0, value, {replace: true, frequency: 10}); + * console.log(result); // Output: "OK" + * ``` + */ + public async restore( + key: GlideString, + ttl: number, + value: Buffer, + options?: RestoreOptions, + ): Promise<"OK"> { + return this.createWritePromise( + createRestore(key, ttl, value, options), + { decoder: Decoder.String }, + ); + } + /** Retrieve the values of multiple keys. - * See https://valkey.io/commands/mget/ for details. * + * @see {@link https://valkey.io/commands/mget/|valkey.io} for details. * @remarks When in cluster mode, the command may route to multiple nodes when `keys` map to different hash slots. + * * @param keys - A list of keys to retrieve values for. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. If not set, the default decoder from the client config will be used. * @returns A list of values corresponding to the provided keys. If a key is not found, * its corresponding value in the list will be null. * @@ -961,14 +1195,18 @@ export class BaseClient { * console.log(result); // Output: ['value1', 'value2'] * ``` */ - public mget(keys: string[]): Promise<(string | null)[]> { - return this.createWritePromise(createMGet(keys)); + public async mget( + keys: GlideString[], + decoder?: Decoder, + ): Promise<(GlideString | null)[]> { + return this.createWritePromise(createMGet(keys), { decoder: decoder }); } /** Set multiple keys to multiple values in a single operation. - * See https://valkey.io/commands/mset/ for details. * + * @see {@link https://valkey.io/commands/mset/|valkey.io} for details. * @remarks When in cluster mode, the command may route to multiple nodes when keys in `keyValueMap` map to different hash slots. + * * @param keyValueMap - A key-value map consisting of keys and their respective values to set. * @returns always "OK". * @@ -979,7 +1217,7 @@ export class BaseClient { * console.log(result); // Output: 'OK' * ``` */ - public mset(keyValueMap: Record): Promise<"OK"> { + public async mset(keyValueMap: Record): Promise<"OK"> { return this.createWritePromise(createMSet(keyValueMap)); } @@ -987,9 +1225,9 @@ export class BaseClient { * Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or * more keys already exist, the entire operation fails. * - * See https://valkey.io/commands/msetnx/ for more details. - * + * @see {@link https://valkey.io/commands/msetnx/|valkey.io} for more details. * @remarks When in cluster mode, all keys in `keyValueMap` must map to the same hash slot. + * * @param keyValueMap - A key-value map consisting of keys and their respective values to set. * @returns `true` if all keys were set. `false` if no key was set. * @@ -1007,7 +1245,8 @@ export class BaseClient { } /** Increments the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/incr/ for details. + * + * @see {@link https://valkey.io/commands/incr/|valkey.io} for details. * * @param key - The key to increment its value. * @returns the value of `key` after the increment. @@ -1020,12 +1259,13 @@ export class BaseClient { * console.log(result); // Output: 11 * ``` */ - public incr(key: string): Promise { + public async incr(key: string): Promise { return this.createWritePromise(createIncr(key)); } /** Increments the number stored at `key` by `amount`. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/incrby/ for details. + * + * @see {@link https://valkey.io/commands/incrby/|valkey.io} for details. * * @param key - The key to increment its value. * @param amount - The amount to increment. @@ -1039,14 +1279,15 @@ export class BaseClient { * console.log(result); // Output: 15 * ``` */ - public incrBy(key: string, amount: number): Promise { + public async incrBy(key: string, amount: number): Promise { return this.createWritePromise(createIncrBy(key, amount)); } /** Increment the string representing a floating point number stored at `key` by `amount`. * By using a negative increment value, the result is that the value stored at `key` is decremented. * If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/incrbyfloat/ for details. + * + * @see {@link https://valkey.io/commands/incrbyfloat/|valkey.io} for details. * * @param key - The key to increment its value. * @param amount - The amount to increment. @@ -1060,12 +1301,13 @@ export class BaseClient { * console.log(result); // Output: 13.0 * ``` */ - public incrByFloat(key: string, amount: number): Promise { + public async incrByFloat(key: string, amount: number): Promise { return this.createWritePromise(createIncrByFloat(key, amount)); } /** Decrements the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/decr/ for details. + * + * @see {@link https://valkey.io/commands/decr/|valkey.io} for details. * * @param key - The key to decrement its value. * @returns the value of `key` after the decrement. @@ -1078,12 +1320,13 @@ export class BaseClient { * console.log(result); // Output: 9 * ``` */ - public decr(key: string): Promise { + public async decr(key: string): Promise { return this.createWritePromise(createDecr(key)); } /** Decrements the number stored at `key` by `amount`. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/decrby/ for details. + * + * @see {@link https://valkey.io/commands/decrby/|valkey.io} for details. * * @param key - The key to decrement its value. * @param amount - The amount to decrement. @@ -1097,7 +1340,7 @@ export class BaseClient { * console.log(result); // Output: 5 * ``` */ - public decrBy(key: string, amount: number): Promise { + public async decrBy(key: string, amount: number): Promise { return this.createWritePromise(createDecrBy(key, amount)); } @@ -1105,9 +1348,9 @@ export class BaseClient { * Perform a bitwise operation between multiple keys (containing string values) and store the result in the * `destination`. * - * See https://valkey.io/commands/bitop/ for more details. - * + * @see {@link https://valkey.io/commands/bitop/|valkey.io} for more details. * @remarks When in cluster mode, `destination` and all `keys` must map to the same hash slot. + * * @param operation - The bitwise operation to perform. * @param destination - The key that will store the resulting string. * @param keys - The list of keys to perform the bitwise operation on. @@ -1124,7 +1367,7 @@ export class BaseClient { * console.log(result2); // Output: "@" - "@" has binary value 01000000 * ``` */ - public bitop( + public async bitop( operation: BitwiseOperation, destination: string, keys: string[], @@ -1138,7 +1381,7 @@ export class BaseClient { * Returns the bit value at `offset` in the string value stored at `key`. `offset` must be greater than or equal * to zero. * - * See https://valkey.io/commands/getbit/ for more details. + * @see {@link https://valkey.io/commands/getbit/|valkey.io} for more details. * * @param key - The key of the string. * @param offset - The index of the bit to return. @@ -1151,7 +1394,7 @@ export class BaseClient { * console.log(result); // Output: 1 - The second bit of the string stored at "key" is set to 1. * ``` */ - public getbit(key: string, offset: number): Promise { + public async getbit(key: string, offset: number): Promise { return this.createWritePromise(createGetBit(key, offset)); } @@ -1161,7 +1404,7 @@ export class BaseClient { * `2^32` and greater than or equal to `0`. If a key is non-existent then the bit at `offset` is set to `value` and * the preceding bits are set to `0`. * - * See https://valkey.io/commands/setbit/ for more details. + * @see {@link https://valkey.io/commands/setbit/|valkey.io} for more details. * * @param key - The key of the string. * @param offset - The index of the bit to be set. @@ -1174,7 +1417,11 @@ export class BaseClient { * console.log(result); // Output: 0 - The second bit value was 0 before setting to 1. * ``` */ - public setbit(key: string, offset: number, value: number): Promise { + public async setbit( + key: string, + offset: number, + value: number, + ): Promise { return this.createWritePromise(createSetBit(key, offset, value)); } @@ -1184,7 +1431,7 @@ export class BaseClient { * The offset can also be a negative number indicating an offset starting at the end of the list, with `-1` being * the last byte of the list, `-2` being the penultimate, and so on. * - * See https://valkey.io/commands/bitpos/ for more details. + * @see {@link https://valkey.io/commands/bitpos/|valkey.io} for more details. * * @param key - The key of the string. * @param bit - The bit value to match. Must be `0` or `1`. @@ -1221,7 +1468,7 @@ export class BaseClient { * are assumed. If BIT is specified, `start=0` and `end=2` means to look at the first three bits. If BYTE is * specified, `start=0` and `end=2` means to look at the first three bytes. * - * See https://valkey.io/commands/bitpos/ for more details. + * @see {@link https://valkey.io/commands/bitpos/|valkey.io} for more details. * * @param key - The key of the string. * @param bit - The bit value to match. Must be `0` or `1`. @@ -1259,7 +1506,7 @@ export class BaseClient { * Reads or modifies the array of bits representing the string that is held at `key` based on the specified * `subcommands`. * - * See https://valkey.io/commands/bitfield/ for more details. + * @see {@link https://valkey.io/commands/bitfield/|valkey.io} for more details. * * @param key - The key of the string. * @param subcommands - The subcommands to be performed on the binary value of the string at `key`, which could be @@ -1296,14 +1543,13 @@ export class BaseClient { /** * Reads the array of bits representing the string that is held at `key` based on the specified `subcommands`. * - * See https://valkey.io/commands/bitfield_ro/ for more details. + * @see {@link https://valkey.io/commands/bitfield_ro/|valkey.io} for more details. + * @remarks Since Valkey version 6.0.0. * * @param key - The key of the string. * @param subcommands - The {@link BitFieldGet} subcommands to be performed. * @returns An array of results from the {@link BitFieldGet} subcommands. * - * since Valkey version 6.0.0. - * * @example * ```typescript * await client.set("key", "A"); // "A" has binary value 01000001 @@ -1319,16 +1565,18 @@ export class BaseClient { } /** Retrieve the value associated with `field` in the hash stored at `key`. - * See https://valkey.io/commands/hget/ for details. + * + * @see {@link https://valkey.io/commands/hget/|valkey.io} for details. * * @param key - The key of the hash. * @param field - The field in the hash stored at `key` to retrieve from the database. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. If not set, the default decoder from the client config will be used. * @returns the value associated with `field`, or null when `field` is not present in the hash or `key` does not exist. * * @example * ```typescript * // Example usage of the hget method on an-existing field - * await client.hset("my_hash", "field"); + * await client.hset("my_hash", {"field": "value"}); * const result = await client.hget("my_hash", "field"); * console.log(result); // Output: "value" * ``` @@ -1340,12 +1588,19 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public hget(key: string, field: string): Promise { - return this.createWritePromise(createHGet(key, field)); + public async hget( + key: GlideString, + field: GlideString, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createHGet(key, field), { + decoder: decoder, + }); } /** Sets the specified fields to their respective values in the hash stored at `key`. - * See https://valkey.io/commands/hset/ for details. + * + * @see {@link https://valkey.io/commands/hset/|valkey.io} for details. * * @param key - The key of the hash. * @param fieldValueMap - A field-value map consisting of fields and their corresponding values @@ -1355,21 +1610,42 @@ export class BaseClient { * @example * ```typescript * // Example usage of the hset method - * const result = await client.hset("my_hash", \{"field": "value", "field2": "value2"\}); + * const result = await client.hset("my_hash", {"field": "value", "field2": "value2"}); * console.log(result); // Output: 2 - Indicates that 2 fields were successfully set in the hash "my_hash". * ``` */ - public hset( + public async hset( key: string, fieldValueMap: Record, ): Promise { return this.createWritePromise(createHSet(key, fieldValueMap)); } + /** + * Returns all field names in the hash stored at `key`. + * + * @see {@link https://valkey.io/commands/hkeys/|valkey.io} for details. + * + * @param key - The key of the hash. + * @returns A list of field names for the hash, or an empty list when the key does not exist. + * + * @example + * ```typescript + * // Example usage of the hkeys method: + * await client.hset("my_hash", {"field1": "value1", "field2": "value2", "field3": "value3"}); + * const result = await client.hkeys("my_hash"); + * console.log(result); // Output: ["field1", "field2", "field3"] - Returns all the field names stored in the hash "my_hash". + * ``` + */ + public hkeys(key: string): Promise { + return this.createWritePromise(createHKeys(key)); + } + /** Sets `field` in the hash stored at `key` to `value`, only if `field` does not yet exist. * If `key` does not exist, a new key holding a hash is created. * If `field` already exists, this operation has no effect. - * See https://valkey.io/commands/hsetnx/ for more details. + * + * @see {@link https://valkey.io/commands/hsetnx/|valkey.io} for more details. * * @param key - The key of the hash. * @param field - The field to set the value for. @@ -1390,13 +1666,18 @@ export class BaseClient { * console.log(result); // Output: false - Indicates that the field "field" already existed in the hash "my_hash" and was not set again. * ``` */ - public hsetnx(key: string, field: string, value: string): Promise { + public async hsetnx( + key: string, + field: string, + value: string, + ): Promise { return this.createWritePromise(createHSetNX(key, field, value)); } /** Removes the specified fields from the hash stored at `key`. * Specified fields that do not exist within this hash are ignored. - * See https://valkey.io/commands/hdel/ for details. + * + * @see {@link https://valkey.io/commands/hdel/|valkey.io} for details. * * @param key - The key of the hash. * @param fields - The fields to remove from the hash stored at `key`. @@ -1410,12 +1691,13 @@ export class BaseClient { * console.log(result); // Output: 2 - Indicates that two fields were successfully removed from the hash. * ``` */ - public hdel(key: string, fields: string[]): Promise { + public async hdel(key: string, fields: string[]): Promise { return this.createWritePromise(createHDel(key, fields)); } /** Returns the values associated with the specified fields in the hash stored at `key`. - * See https://valkey.io/commands/hmget/ for details. + * + * @see {@link https://valkey.io/commands/hmget/|valkey.io} for details. * * @param key - The key of the hash. * @param fields - The fields in the hash stored at `key` to retrieve from the database. @@ -1430,12 +1712,16 @@ export class BaseClient { * console.log(result); // Output: ["value1", "value2"] - A list of values associated with the specified fields. * ``` */ - public hmget(key: string, fields: string[]): Promise<(string | null)[]> { + public async hmget( + key: string, + fields: string[], + ): Promise<(string | null)[]> { return this.createWritePromise(createHMGet(key, fields)); } /** Returns if `field` is an existing field in the hash stored at `key`. - * See https://valkey.io/commands/hexists/ for details. + * + * @see {@link https://valkey.io/commands/hexists/|valkey.io} for details. * * @param key - The key of the hash. * @param field - The field to check in the hash stored at `key`. @@ -1455,12 +1741,13 @@ export class BaseClient { * console.log(result); // Output: false * ``` */ - public hexists(key: string, field: string): Promise { + public async hexists(key: string, field: string): Promise { return this.createWritePromise(createHExists(key, field)); } /** Returns all fields and values of the hash stored at `key`. - * See https://valkey.io/commands/hgetall/ for details. + * + * @see {@link https://valkey.io/commands/hgetall/|valkey.io} for details. * * @param key - The key of the hash. * @returns a list of fields and their values stored in the hash. Every field name in the list is followed by its value. @@ -1473,14 +1760,15 @@ export class BaseClient { * console.log(result); // Output: {"field1": "value1", "field2": "value2"} * ``` */ - public hgetall(key: string): Promise> { + public async hgetall(key: string): Promise> { return this.createWritePromise(createHGetAll(key)); } /** Increments the number stored at `field` in the hash stored at `key` by increment. * By using a negative increment value, the value stored at `field` in the hash stored at `key` is decremented. * If `field` or `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/hincrby/ for details. + * + * @see {@link https://valkey.io/commands/hincrby/|valkey.io} for details. * * @param key - The key of the hash. * @param amount - The amount to increment. @@ -1494,7 +1782,7 @@ export class BaseClient { * console.log(result); // Output: 5 * ``` */ - public hincrBy( + public async hincrBy( key: string, field: string, amount: number, @@ -1505,7 +1793,8 @@ export class BaseClient { /** Increment the string representing a floating point number stored at `field` in the hash stored at `key` by increment. * By using a negative increment value, the value stored at `field` in the hash stored at `key` is decremented. * If `field` or `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/hincrbyfloat/ for details. + * + * @see {@link https://valkey.io/commands/hincrbyfloat/|valkey.io} for details. * * @param key - The key of the hash. * @param amount - The amount to increment. @@ -1519,7 +1808,7 @@ export class BaseClient { * console.log(result); // Output: '2.5' * ``` */ - public hincrByFloat( + public async hincrByFloat( key: string, field: string, amount: number, @@ -1528,7 +1817,8 @@ export class BaseClient { } /** Returns the number of fields contained in the hash stored at `key`. - * See https://valkey.io/commands/hlen/ for more details. + * + * @see {@link https://valkey.io/commands/hlen/|valkey.io} for more details. * * @param key - The key of the hash. * @returns The number of fields in the hash, or 0 when the key does not exist. @@ -1547,14 +1837,16 @@ export class BaseClient { * console.log(result); // Output: 0 * ``` */ - public hlen(key: string): Promise { + public async hlen(key: string): Promise { return this.createWritePromise(createHLen(key)); } /** Returns all values in the hash stored at key. - * See https://valkey.io/commands/hvals/ for more details. + * + * @see {@link https://valkey.io/commands/hvals/|valkey.io} for more details. * * @param key - The key of the hash. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. If not set, the default decoder from the client config will be used. * @returns a list of values in the hash, or an empty list when the key does not exist. * * @example @@ -1564,14 +1856,17 @@ export class BaseClient { * console.log(result); // Output: ["value1", "value2", "value3"] - Returns all the values stored in the hash "my_hash". * ``` */ - public hvals(key: string): Promise { - return this.createWritePromise(createHVals(key)); + public async hvals( + key: GlideString, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createHVals(key), { decoder: decoder }); } /** * Returns the string length of the value associated with `field` in the hash stored at `key`. * - * See https://valkey.io/commands/hstrlen/ for details. + * @see {@link https://valkey.io/commands/hstrlen/|valkey.io} for details. * * @param key - The key of the hash. * @param field - The field in the hash. @@ -1584,14 +1879,132 @@ export class BaseClient { * console.log(result); // Output: 5 * ``` */ - public hstrlen(key: string, field: string): Promise { + public async hstrlen(key: string, field: string): Promise { return this.createWritePromise(createHStrlen(key, field)); } + /** + * Returns a random field name from the hash value stored at `key`. + * + * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the hash. + * @returns A random field name from the hash stored at `key`, or `null` when + * the key does not exist. + * + * @example + * ```typescript + * console.log(await client.hrandfield("myHash")); // Output: 'field' + * ``` + */ + public async hrandfield(key: string): Promise { + return this.createWritePromise(createHRandField(key)); + } + + /** + * Iterates incrementally over a hash. + * + * @see {@link https://valkey.io/commands/hscan/|valkey.io} for more details. + * + * @param key - The key of the set. + * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of the search. + * @param options - (Optional) The {@link BaseScanOptions}. + * @returns An array of the `cursor` and the subset of the hash held by `key`. + * The first element is always the `cursor` for the next iteration of results. `"0"` will be the `cursor` + * returned on the last iteration of the hash. The second element is always an array of the subset of the + * hash held in `key`. The array in the second element is always a flattened series of string pairs, + * where the value is at even indices and the value is at odd indices. + * + * @example + * ```typescript + * // Assume "key" contains a hash with multiple members + * let newCursor = "0"; + * let result = []; + * do { + * result = await client.hscan(key1, newCursor, { + * match: "*", + * count: 3, + * }); + * newCursor = result[0]; + * console.log("Cursor: ", newCursor); + * console.log("Members: ", result[1]); + * } while (newCursor !== "0"); + * // The output of the code above is something similar to: + * // Cursor: 31 + * // Members: ['field 79', 'value 79', 'field 20', 'value 20', 'field 115', 'value 115'] + * // Cursor: 39 + * // Members: ['field 63', 'value 63', 'field 293', 'value 293', 'field 162', 'value 162'] + * // Cursor: 0 + * // Members: ['value 55', '55', 'value 24', '24', 'value 90', '90', 'value 113', '113'] + * ``` + */ + public async hscan( + key: string, + cursor: string, + options?: BaseScanOptions, + ): Promise<[string, string[]]> { + return this.createWritePromise(createHScan(key, cursor, options)); + } + + /** + * Retrieves up to `count` random field names from the hash value stored at `key`. + * + * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the hash. + * @param count - The number of field names to return. + * + * If `count` is positive, returns unique elements. If negative, allows for duplicates. + * @returns An `array` of random field names from the hash stored at `key`, + * or an `empty array` when the key does not exist. + * + * @example + * ```typescript + * console.log(await client.hrandfieldCount("myHash", 2)); // Output: ['field1', 'field2'] + * ``` + */ + public async hrandfieldCount( + key: string, + count: number, + ): Promise { + return this.createWritePromise(createHRandField(key, count)); + } + + /** + * Retrieves up to `count` random field names along with their values from the hash + * value stored at `key`. + * + * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the hash. + * @param count - The number of field names to return. + * + * If `count` is positive, returns unique elements. If negative, allows for duplicates. + * @returns A 2D `array` of `[fieldName, value]` `arrays`, where `fieldName` is a random + * field name from the hash and `value` is the associated value of the field name. + * If the hash does not exist or is empty, the response will be an empty `array`. + * + * @example + * ```typescript + * const result = await client.hrandfieldCountWithValues("myHash", 2); + * console.log(result); // Output: [['field1', 'value1'], ['field2', 'value2']] + * ``` + */ + public async hrandfieldWithValues( + key: string, + count: number, + ): Promise<[string, string][]> { + return this.createWritePromise(createHRandField(key, count, true)); + } + /** Inserts all the specified values at the head of the list stored at `key`. * `elements` are inserted one after the other to the head of the list, from the leftmost element to the rightmost element. * If `key` does not exist, it is created as empty list before performing the push operations. - * See https://valkey.io/commands/lpush/ for details. + * + * @see {@link https://valkey.io/commands/lpush/|valkey.io} for details. * * @param key - The key of the list. * @param elements - The elements to insert at the head of the list stored at `key`. @@ -1611,7 +2024,7 @@ export class BaseClient { * console.log(result); // Output: 1 - Indicates that a new list was created with one element * ``` */ - public lpush(key: string, elements: string[]): Promise { + public async lpush(key: string, elements: string[]): Promise { return this.createWritePromise(createLPush(key, elements)); } @@ -1619,7 +2032,7 @@ export class BaseClient { * Inserts specified values at the head of the `list`, only if `key` already * exists and holds a list. * - * See https://valkey.io/commands/lpushx/ for details. + * @see {@link https://valkey.io/commands/lpushx/|valkey.io} for details. * * @param key - The key of the list. * @param elements - The elements to insert at the head of the list stored at `key`. @@ -1630,13 +2043,14 @@ export class BaseClient { * console.log(result); // Output: 2 - Indicates that the list has two elements. * ``` */ - public lpushx(key: string, elements: string[]): Promise { + public async lpushx(key: string, elements: string[]): Promise { return this.createWritePromise(createLPushX(key, elements)); } /** Removes and returns the first elements of the list stored at `key`. * The command pops a single element from the beginning of the list. - * See https://valkey.io/commands/lpop/ for details. + * + * @see {@link https://valkey.io/commands/lpop/|valkey.io} for details. * * @param key - The key of the list. * @returns The value of the first element. @@ -1656,12 +2070,13 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public lpop(key: string): Promise { + public async lpop(key: string): Promise { return this.createWritePromise(createLPop(key)); } /** Removes and returns up to `count` elements of the list stored at `key`, depending on the list's length. - * See https://valkey.io/commands/lpop/ for details. + * + * @see {@link https://valkey.io/commands/lpop/|valkey.io} for details. * * @param key - The key of the list. * @param count - The count of the elements to pop from the list. @@ -1682,7 +2097,10 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public lpopCount(key: string, count: number): Promise { + public async lpopCount( + key: string, + count: number, + ): Promise { return this.createWritePromise(createLPop(key, count)); } @@ -1690,7 +2108,8 @@ export class BaseClient { * The offsets `start` and `end` are zero-based indexes, with 0 being the first element of the list, 1 being the next element and so on. * These offsets can also be negative numbers indicating offsets starting at the end of the list, * with -1 being the last element of the list, -2 being the penultimate, and so on. - * See https://valkey.io/commands/lrange/ for details. + * + * @see {@link https://valkey.io/commands/lrange/|valkey.io} for details. * * @param key - The key of the list. * @param start - The starting point of the range. @@ -1721,12 +2140,17 @@ export class BaseClient { * console.log(result); // Output: [] * ``` */ - public lrange(key: string, start: number, end: number): Promise { + public async lrange( + key: string, + start: number, + end: number, + ): Promise { return this.createWritePromise(createLRange(key, start, end)); } /** Returns the length of the list stored at `key`. - * See https://valkey.io/commands/llen/ for details. + * + * @see {@link https://valkey.io/commands/llen/|valkey.io} for details. * * @param key - The key of the list. * @returns the length of the list at `key`. @@ -1739,7 +2163,7 @@ export class BaseClient { * console.log(result); // Output: 3 - Indicates that there are 3 elements in the list. * ``` */ - public llen(key: string): Promise { + public async llen(key: string): Promise { return this.createWritePromise(createLLen(key)); } @@ -1748,7 +2172,8 @@ export class BaseClient { * depending on `whereTo`, and pushes the element at the first/last element of the list * stored at `destination` depending on `whereFrom`, see {@link ListDirection}. * - * See https://valkey.io/commands/lmove/ for details. + * @see {@link https://valkey.io/commands/lmove/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. * * @param source - The key to the source list. * @param destination - The key to the destination list. @@ -1756,8 +2181,6 @@ export class BaseClient { * @param whereTo - The {@link ListDirection} to add the element to. * @returns The popped element, or `null` if `source` does not exist. * - * since Valkey version 6.2.0. - * * @example * ```typescript * await client.lpush("testKey1", ["two", "one"]); @@ -1790,11 +2213,10 @@ export class BaseClient { * of the list stored at `destination` depending on `whereTo`. * `BLMOVE` is the blocking variant of {@link lmove}. * - * @remarks - * 1. When in cluster mode, both `source` and `destination` must map to the same hash slot. - * 2. `BLMOVE` is a client blocking command, see https://github.com/aws/glide-for-redis/wiki/General-Concepts#blocking-commands for more details and best practices. - * - * See https://valkey.io/commands/blmove/ for details. + * @see {@link https://valkey.io/commands/blmove/|valkey.io} for details. + * @remarks When in cluster mode, both `source` and `destination` must map to the same hash slot. + * @remarks `BLMOVE` is a client blocking command, see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands|Valkey Glide Wiki} for more details and best practices. + * @remarks Since Valkey version 6.2.0. * * @param source - The key to the source list. * @param destination - The key to the destination list. @@ -1803,8 +2225,6 @@ export class BaseClient { * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of `0` will block indefinitely. * @returns The popped element, or `null` if `source` does not exist or if the operation timed-out. * - * since Valkey version 6.2.0. - * * @example * ```typescript * await client.lpush("testKey1", ["two", "one"]); @@ -1837,7 +2257,7 @@ export class BaseClient { * Negative indices can be used to designate elements starting at the tail of * the list. Here, `-1` means the last element, `-2` means the penultimate and so forth. * - * See https://valkey.io/commands/lset/ for details. + * @see {@link https://valkey.io/commands/lset/|valkey.io} for details. * * @param key - The key of the list. * @param index - The index of the element in the list to be set. @@ -1851,7 +2271,11 @@ export class BaseClient { * console.log(response); // Output: 'OK' - Indicates that the second index of the list has been set to "two". * ``` */ - public lset(key: string, index: number, element: string): Promise<"OK"> { + public async lset( + key: string, + index: number, + element: string, + ): Promise<"OK"> { return this.createWritePromise(createLSet(key, index, element)); } @@ -1859,7 +2283,8 @@ export class BaseClient { * The offsets `start` and `end` are zero-based indexes, with 0 being the first element of the list, 1 being the next element and so on. * These offsets can also be negative numbers indicating offsets starting at the end of the list, * with -1 being the last element of the list, -2 being the penultimate, and so on. - * See https://valkey.io/commands/ltrim/ for details. + * + * @see {@link https://valkey.io/commands/ltrim/|valkey.io} for details. * * @param key - The key of the list. * @param start - The starting point of the range. @@ -1876,7 +2301,7 @@ export class BaseClient { * console.log(result); // Output: 'OK' - Indicates that the list has been trimmed to contain elements from 0 to 1. * ``` */ - public ltrim(key: string, start: number, end: number): Promise<"OK"> { + public async ltrim(key: string, start: number, end: number): Promise<"OK"> { return this.createWritePromise(createLTrim(key, start, end)); } @@ -1898,14 +2323,19 @@ export class BaseClient { * console.log(result); // Output: 2 - Removes the first 2 occurrences of "value" in the list. * ``` */ - public lrem(key: string, count: number, element: string): Promise { + public async lrem( + key: string, + count: number, + element: string, + ): Promise { return this.createWritePromise(createLRem(key, count, element)); } /** Inserts all the specified values at the tail of the list stored at `key`. * `elements` are inserted one after the other to the tail of the list, from the leftmost element to the rightmost element. * If `key` does not exist, it is created as empty list before performing the push operations. - * See https://valkey.io/commands/rpush/ for details. + * + * @see {@link https://valkey.io/commands/rpush/|valkey.io} for details. * * @param key - The key of the list. * @param elements - The elements to insert at the tail of the list stored at `key`. @@ -1925,7 +2355,7 @@ export class BaseClient { * console.log(result); // Output: 1 * ``` */ - public rpush(key: string, elements: string[]): Promise { + public async rpush(key: string, elements: string[]): Promise { return this.createWritePromise(createRPush(key, elements)); } @@ -1933,7 +2363,7 @@ export class BaseClient { * Inserts specified values at the tail of the `list`, only if `key` already * exists and holds a list. * - * See https://valkey.io/commands/rpushx/ for details. + * @see {@link https://valkey.io/commands/rpushx/|valkey.io} for details. * * @param key - The key of the list. * @param elements - The elements to insert at the tail of the list stored at `key`. @@ -1944,13 +2374,14 @@ export class BaseClient { * console.log(result); // Output: 2 - Indicates that the list has two elements. * ``` * */ - public rpushx(key: string, elements: string[]): Promise { + public async rpushx(key: string, elements: string[]): Promise { return this.createWritePromise(createRPushX(key, elements)); } /** Removes and returns the last elements of the list stored at `key`. * The command pops a single element from the end of the list. - * See https://valkey.io/commands/rpop/ for details. + * + * @see {@link https://valkey.io/commands/rpop/|valkey.io} for details. * * @param key - The key of the list. * @returns The value of the last element. @@ -1970,12 +2401,13 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public rpop(key: string): Promise { + public async rpop(key: string): Promise { return this.createWritePromise(createRPop(key)); } /** Removes and returns up to `count` elements from the list stored at `key`, depending on the list's length. - * See https://valkey.io/commands/rpop/ for details. + * + * @see {@link https://valkey.io/commands/rpop/|valkey.io} for details. * * @param key - The key of the list. * @param count - The count of the elements to pop from the list. @@ -1996,13 +2428,17 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public rpopCount(key: string, count: number): Promise { + public async rpopCount( + key: string, + count: number, + ): Promise { return this.createWritePromise(createRPop(key, count)); } /** Adds the specified members to the set stored at `key`. Specified members that are already a member of this set are ignored. * If `key` does not exist, a new set is created before adding `members`. - * See https://valkey.io/commands/sadd/ for details. + * + * @see {@link https://valkey.io/commands/sadd/|valkey.io} for details. * * @param key - The key to store the members to its set. * @param members - A list of members to add to the set stored at `key`. @@ -2015,12 +2451,13 @@ export class BaseClient { * console.log(result); // Output: 2 * ``` */ - public sadd(key: string, members: string[]): Promise { + public async sadd(key: string, members: string[]): Promise { return this.createWritePromise(createSAdd(key, members)); } /** Removes the specified members from the set stored at `key`. Specified members that are not a member of this set are ignored. - * See https://valkey.io/commands/srem/ for details. + * + * @see {@link https://valkey.io/commands/srem/|valkey.io} for details. * * @param key - The key to remove the members from its set. * @param members - A list of members to remove from the set stored at `key`. @@ -2034,12 +2471,57 @@ export class BaseClient { * console.log(result); // Output: 2 * ``` */ - public srem(key: string, members: string[]): Promise { + public async srem(key: string, members: string[]): Promise { return this.createWritePromise(createSRem(key, members)); } + /** + * Iterates incrementally over a set. + * + * @see {@link https://valkey.io/commands/sscan} for details. + * + * @param key - The key of the set. + * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of the search. + * @param options - The (Optional) {@link BaseScanOptions}. + * @returns An array of the cursor and the subset of the set held by `key`. The first element is always the `cursor` and for the next iteration of results. + * The `cursor` will be `"0"` on the last iteration of the set. The second element is always an array of the subset of the set held in `key`. + * + * @example + * ```typescript + * // Assume key contains a set with 200 members + * let newCursor = "0"; + * let result = []; + * + * do { + * result = await client.sscan(key1, newCursor, { + * match: "*", + * count: 5, + * }); + * newCursor = result[0]; + * console.log("Cursor: ", newCursor); + * console.log("Members: ", result[1]); + * } while (newCursor !== "0"); + * + * // The output of the code above is something similar to: + * // Cursor: 8, Match: "f*" + * // Members: ['field', 'fur', 'fun', 'fame'] + * // Cursor: 20, Count: 3 + * // Members: ['1', '2', '3', '4', '5', '6'] + * // Cursor: 0 + * // Members: ['1', '2', '3', '4', '5', '6'] + * ``` + */ + public async sscan( + key: string, + cursor: string, + options?: BaseScanOptions, + ): Promise<[string, string[]]> { + return this.createWritePromise(createSScan(key, cursor, options)); + } + /** Returns all the members of the set value stored at `key`. - * See https://valkey.io/commands/smembers/ for details. + * + * @see {@link https://valkey.io/commands/smembers/|valkey.io} for details. * * @param key - The key to return its members. * @returns A `Set` containing all members of the set. @@ -2052,7 +2534,7 @@ export class BaseClient { * console.log(result); // Output: Set {'member1', 'member2', 'member3'} * ``` */ - public smembers(key: string): Promise> { + public async smembers(key: string): Promise> { return this.createWritePromise(createSMembers(key)).then( (smembes) => new Set(smembes), ); @@ -2060,8 +2542,8 @@ export class BaseClient { /** Moves `member` from the set at `source` to the set at `destination`, removing it from the source set. * Creates a new destination set if needed. The operation is atomic. - * See https://valkey.io/commands/smove for more details. * + * @see {@link https://valkey.io/commands/smove/|valkey.io} for more details. * @remarks When in cluster mode, `source` and `destination` must map to the same hash slot. * * @param source - The key of the set to remove the element from. @@ -2075,7 +2557,7 @@ export class BaseClient { * console.log(result); // Output: true - "member1" was moved from "set1" to "set2". * ``` */ - public smove( + public async smove( source: string, destination: string, member: string, @@ -2086,7 +2568,8 @@ export class BaseClient { } /** Returns the set cardinality (number of elements) of the set stored at `key`. - * See https://valkey.io/commands/scard/ for details. + * + * @see {@link https://valkey.io/commands/scard/|valkey.io} for details. * * @param key - The key to return the number of its members. * @returns The cardinality (number of elements) of the set, or 0 if key does not exist. @@ -2098,14 +2581,15 @@ export class BaseClient { * console.log(result); // Output: 3 * ``` */ - public scard(key: string): Promise { + public async scard(key: string): Promise { return this.createWritePromise(createSCard(key)); } /** Gets the intersection of all the given sets. - * See https://valkey.io/docs/latest/commands/sinter/ for more details. * + * @see {@link https://valkey.io/docs/latest/commands/sinter/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * * @param keys - The `keys` of the sets to get the intersection. * @returns - A set of members which are present in all given sets. * If one or more sets do not exist, an empty set will be returned. @@ -2124,7 +2608,7 @@ export class BaseClient { * console.log(result); // Output: Set {} - An empty set is returned since the key does not exist. * ``` */ - public sinter(keys: string[]): Promise> { + public async sinter(keys: string[]): Promise> { return this.createWritePromise(createSInter(keys)).then( (sinter) => new Set(sinter), ); @@ -2133,15 +2617,14 @@ export class BaseClient { /** * Gets the cardinality of the intersection of all the given sets. * - * See https://valkey.io/commands/sintercard/ for more details. - * + * @see {@link https://valkey.io/commands/sintercard/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * * @param keys - The keys of the sets. * @param limit - The limit for the intersection cardinality value. If not specified, or set to `0`, no limit is used. * @returns The cardinality of the intersection result. If one or more sets do not exist, `0` is returned. * - * since Valkey version 7.0.0. - * * @example * ```typescript * await client.sadd("set1", ["a", "b", "c"]); @@ -2153,16 +2636,16 @@ export class BaseClient { * console.log(result2); // Output: 1 - The computation stops early as the intersection cardinality reaches the limit of 1. * ``` */ - public sintercard(keys: string[], limit?: number): Promise { + public async sintercard(keys: string[], limit?: number): Promise { return this.createWritePromise(createSInterCard(keys, limit)); } /** * Stores the members of the intersection of all given sets specified by `keys` into a new set at `destination`. * - * See https://valkey.io/commands/sinterstore/ for more details. - * + * @see {@link https://valkey.io/commands/sinterstore/|valkey.io} for more details. * @remarks When in cluster mode, `destination` and all `keys` must map to the same hash slot. + * * @param destination - The key of the destination set. * @param keys - The keys from which to retrieve the set members. * @returns The number of elements in the resulting set. @@ -2173,16 +2656,19 @@ export class BaseClient { * console.log(result); // Output: 2 - Two elements were stored at "my_set", and those elements are the intersection of "set1" and "set2". * ``` */ - public sinterstore(destination: string, keys: string[]): Promise { + public async sinterstore( + destination: string, + keys: string[], + ): Promise { return this.createWritePromise(createSInterStore(destination, keys)); } /** * Computes the difference between the first set and all the successive sets in `keys`. * - * See https://valkey.io/commands/sdiff/ for more details. - * + * @see {@link https://valkey.io/commands/sdiff/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * * @param keys - The keys of the sets to diff. * @returns A `Set` of elements representing the difference between the sets. * If a key in `keys` does not exist, it is treated as an empty set. @@ -2195,7 +2681,7 @@ export class BaseClient { * console.log(result); // Output: Set {"member1"} - "member2" is in "set1" but not "set2" * ``` */ - public sdiff(keys: string[]): Promise> { + public async sdiff(keys: string[]): Promise> { return this.createWritePromise(createSDiff(keys)).then( (sdiff) => new Set(sdiff), ); @@ -2204,9 +2690,9 @@ export class BaseClient { /** * Stores the difference between the first set and all the successive sets in `keys` into a new set at `destination`. * - * See https://valkey.io/commands/sdiffstore/ for more details. - * + * @see {@link https://valkey.io/commands/sdiffstore/|valkey.io} for more details. * @remarks When in cluster mode, `destination` and all `keys` must map to the same hash slot. + * * @param destination - The key of the destination set. * @param keys - The keys of the sets to diff. * @returns The number of elements in the resulting set. @@ -2219,16 +2705,19 @@ export class BaseClient { * console.log(result); // Output: 1 - One member was stored in "set3", and that member is the diff between "set1" and "set2". * ``` */ - public sdiffstore(destination: string, keys: string[]): Promise { + public async sdiffstore( + destination: string, + keys: string[], + ): Promise { return this.createWritePromise(createSDiffStore(destination, keys)); } /** * Gets the union of all the given sets. * - * See https://valkey.io/commands/sunion/ for more details. - * + * @see {@link https://valkey.io/commands/sunion/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * * @param keys - The keys of the sets. * @returns A `Set` of members which are present in at least one of the given sets. * If none of the sets exist, an empty `Set` will be returned. @@ -2244,7 +2733,7 @@ export class BaseClient { * console.log(result2); // Output: Set {'member1', 'member2'} * ``` */ - public sunion(keys: string[]): Promise> { + public async sunion(keys: string[]): Promise> { return this.createWritePromise(createSUnion(keys)).then( (sunion) => new Set(sunion), ); @@ -2254,9 +2743,9 @@ export class BaseClient { * Stores the members of the union of all given sets specified by `keys` into a new set * at `destination`. * - * See https://valkey.io/commands/sunionstore/ for details. - * + * @see {@link https://valkey.io/commands/sunionstore/|valkey.io} for details. * @remarks When in cluster mode, `destination` and all `keys` must map to the same hash slot. + * * @param destination - The key of the destination set. * @param keys - The keys from which to retrieve the set members. * @returns The number of elements in the resulting set. @@ -2267,12 +2756,16 @@ export class BaseClient { * console.log(length); // Output: 2 - Two elements were stored in "mySet", and those two members are the union of "set1" and "set2". * ``` */ - public sunionstore(destination: string, keys: string[]): Promise { + public async sunionstore( + destination: string, + keys: string[], + ): Promise { return this.createWritePromise(createSUnionStore(destination, keys)); } /** Returns if `member` is a member of the set stored at `key`. - * See https://valkey.io/commands/sismember/ for more details. + * + * @see {@link https://valkey.io/commands/sismember/|valkey.io} for more details. * * @param key - The key of the set. * @param member - The member to check for existence in the set. @@ -2293,21 +2786,20 @@ export class BaseClient { * console.log(result); // Output: false - Indicates that "non_existing_member" does not exist in the set "my_set". * ``` */ - public sismember(key: string, member: string): Promise { + public async sismember(key: string, member: string): Promise { return this.createWritePromise(createSIsMember(key, member)); } /** * Checks whether each member is contained in the members of the set stored at `key`. * - * See https://valkey.io/commands/smismember/ for more details. + * @see {@link https://valkey.io/commands/smismember/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. * * @param key - The key of the set to check. * @param members - A list of members to check for existence in the set. * @returns An `array` of `boolean` values, each indicating if the respective member exists in the set. * - * since Valkey version 6.2.0. - * * @example * ```typescript * await client.sadd("set1", ["a", "b", "c"]); @@ -2315,13 +2807,17 @@ export class BaseClient { * console.log(result); // Output: [true, true, false] - "b" and "c" are members of "set1", but "d" is not. * ``` */ - public smismember(key: string, members: string[]): Promise { + public async smismember( + key: string, + members: string[], + ): Promise { return this.createWritePromise(createSMIsMember(key, members)); } /** Removes and returns one random member from the set value store at `key`. - * See https://valkey.io/commands/spop/ for details. - * To pop multiple members, see `spopCount`. + * To pop multiple members, see {@link spopCount}. + * + * @see {@link https://valkey.io/commands/spop/|valkey.io} for details. * * @param key - The key of the set. * @returns the value of the popped member. @@ -2341,12 +2837,13 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public spop(key: string): Promise { + public async spop(key: string): Promise { return this.createWritePromise(createSPop(key)); } /** Removes and returns up to `count` random members from the set value store at `key`, depending on the set's length. - * See https://valkey.io/commands/spop/ for details. + * + * @see {@link https://valkey.io/commands/spop/|valkey.io} for details. * * @param key - The key of the set. * @param count - The count of the elements to pop from the set. @@ -2373,8 +2870,67 @@ export class BaseClient { ); } + /** + * Returns a random element from the set value stored at `key`. + * + * @see {@link https://valkey.io/commands/srandmember/|valkey.io} for more details. + * + * @param key - The key from which to retrieve the set member. + * @returns A random element from the set, or null if `key` does not exist. + * + * @example + * ```typescript + * // Example usage of srandmember method to return a random member from a set + * const result = await client.srandmember("my_set"); + * console.log(result); // Output: 'member1' - A random member of "my_set". + * ``` + * + * @example + * ```typescript + * // Example usage of srandmember method with non-existing key + * const result = await client.srandmember("non_existing_set"); + * console.log(result); // Output: null + * ``` + */ + public async srandmember(key: string): Promise { + return this.createWritePromise(createSRandMember(key)); + } + + /** + * Returns one or more random elements from the set value stored at `key`. + * + * @see {@link https://valkey.io/commands/srandmember/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param count - The number of members to return. + * If `count` is positive, returns unique members. + * If `count` is negative, allows for duplicates members. + * @returns a list of members from the set. If the set does not exist or is empty, an empty list will be returned. + * + * @example + * ```typescript + * // Example usage of srandmemberCount method to return multiple random members from a set + * const result = await client.srandmemberCount("my_set", -3); + * console.log(result); // Output: ['member1', 'member1', 'member2'] - Random members of "my_set". + * ``` + * + * @example + * ```typescript + * // Example usage of srandmemberCount method with non-existing key + * const result = await client.srandmemberCount("non_existing_set", 3); + * console.log(result); // Output: [] - An empty list since the key does not exist. + * ``` + */ + public async srandmemberCount( + key: string, + count: number, + ): Promise { + return this.createWritePromise(createSRandMember(key, count)); + } + /** Returns the number of keys in `keys` that exist in the database. - * See https://valkey.io/commands/exists/ for details. + * + * @see {@link https://valkey.io/commands/exists/|valkey.io} for details. * * @param keys - The keys list to check. * @returns The number of keys that exist. If the same existing key is mentioned in `keys` multiple times, @@ -2387,14 +2943,15 @@ export class BaseClient { * console.log(result); // Output: 3 - Indicates that all three keys exist in the database. * ``` */ - public exists(keys: string[]): Promise { + public async exists(keys: string[]): Promise { return this.createWritePromise(createExists(keys)); } /** Removes the specified keys. A key is ignored if it does not exist. * This command, similar to DEL, removes specified keys and ignores non-existent ones. * However, this command does not block the server, while [DEL](https://valkey.io/commands/del) does. - * See https://valkey.io/commands/unlink/ for details. + * + * @see {@link https://valkey.io/commands/unlink/|valkey.io} for details. * * @param keys - The keys we wanted to unlink. * @returns The number of keys that were unlinked. @@ -2406,7 +2963,7 @@ export class BaseClient { * console.log(result); // Output: 3 - Indicates that all three keys were unlinked from the database. * ``` */ - public unlink(keys: string[]): Promise { + public async unlink(keys: string[]): Promise { return this.createWritePromise(createUnlink(keys)); } @@ -2414,7 +2971,8 @@ export class BaseClient { * If `key` already has an existing expire set, the time to live is updated to the new value. * If `seconds` is non-positive number, the key will be deleted rather than expired. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/expire/ for details. + * + * @see {@link https://valkey.io/commands/expire/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param seconds - The timeout in seconds. @@ -2436,7 +2994,7 @@ export class BaseClient { * console.log(result); // Output: false - Indicates that "my_key" has an existing expiry. * ``` */ - public expire( + public async expire( key: string, seconds: number, option?: ExpireOptions, @@ -2448,7 +3006,8 @@ export class BaseClient { * A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be deleted. * If `key` already has an existing expire set, the time to live is updated to the new value. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/expireat/ for details. + * + * @see {@link https://valkey.io/commands/expireat/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param unixSeconds - The timeout in an absolute Unix timestamp. @@ -2463,7 +3022,7 @@ export class BaseClient { * console.log(result); // Output: true - Indicates that the expiration time for "my_key" was successfully set. * ``` */ - public expireAt( + public async expireAt( key: string, unixSeconds: number, option?: ExpireOptions, @@ -2477,13 +3036,12 @@ export class BaseClient { * Returns the absolute Unix timestamp (since January 1, 1970) at which the given `key` will expire, in seconds. * To get the expiration with millisecond precision, use {@link pexpiretime}. * - * See https://valkey.io/commands/expiretime/ for details. + * @see {@link https://valkey.io/commands/expiretime/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. * * @param key - The `key` to determine the expiration value of. * @returns The expiration Unix timestamp in seconds, `-2` if `key` does not exist or `-1` if `key` exists but has no associated expire. * - * since Valkey version 7.0.0. - * * @example * ```typescript * const result1 = await client.expiretime("myKey"); @@ -2506,7 +3064,8 @@ export class BaseClient { * If `key` already has an existing expire set, the time to live is updated to the new value. * If `milliseconds` is non-positive number, the key will be deleted rather than expired. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/pexpire/ for details. + * + * @see {@link https://valkey.io/commands/pexpire/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param milliseconds - The timeout in milliseconds. @@ -2521,7 +3080,7 @@ export class BaseClient { * console.log(result); // Output: true - Indicates that a timeout of 60,000 milliseconds has been set for "my_key". * ``` */ - public pexpire( + public async pexpire( key: string, milliseconds: number, option?: ExpireOptions, @@ -2535,7 +3094,8 @@ export class BaseClient { * A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be deleted. * If `key` already has an existing expire set, the time to live is updated to the new value. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/pexpireat/ for details. + * + * @see {@link https://valkey.io/commands/pexpireat/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param unixMilliseconds - The timeout in an absolute Unix timestamp. @@ -2550,7 +3110,7 @@ export class BaseClient { * console.log(result); // Output: true - Indicates that the expiration time for "my_key" was successfully set. * ``` */ - public pexpireAt( + public async pexpireAt( key: string, unixMilliseconds: number, option?: ExpireOptions, @@ -2563,13 +3123,12 @@ export class BaseClient { /** * Returns the absolute Unix timestamp (since January 1, 1970) at which the given `key` will expire, in milliseconds. * - * See https://valkey.io/commands/pexpiretime/ for details. + * @see {@link https://valkey.io/commands/pexpiretime/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. * * @param key - The `key` to determine the expiration value of. * @returns The expiration Unix timestamp in seconds, `-2` if `key` does not exist or `-1` if `key` exists but has no associated expire. * - * since Valkey version 7.0.0. - * * @example * ```typescript * const result1 = client.pexpiretime("myKey"); @@ -2589,7 +3148,8 @@ export class BaseClient { } /** Returns the remaining time to live of `key` that has a timeout. - * See https://valkey.io/commands/ttl/ for details. + * + * @see {@link https://valkey.io/commands/ttl/|valkey.io} for details. * * @param key - The key to return its timeout. * @returns TTL in seconds, -2 if `key` does not exist or -1 if `key` exists but has no associated expire. @@ -2615,15 +3175,16 @@ export class BaseClient { * console.log(result); // Output: -2 - Indicates that the key doesn't exist. * ``` */ - public ttl(key: string): Promise { + public async ttl(key: string): Promise { return this.createWritePromise(createTTL(key)); } /** Invokes a Lua script with its keys and arguments. - * This method simplifies the process of invoking scripts on a Redis server by using an object that represents a Lua script. + * This method simplifies the process of invoking scripts on a Valkey server by using an object that represents a Lua script. * The script loading, argument preparation, and execution will all be handled internally. If the script has not already been loaded, - * it will be loaded automatically using the Redis `SCRIPT LOAD` command. After that, it will be invoked using the Redis `EVALSHA` command - * See https://valkey.io/commands/script-load/ and https://valkey.io/commands/evalsha/ for details. + * it will be loaded automatically using the `SCRIPT LOAD` command. After that, it will be invoked using the `EVALSHA` command. + * + * @see {@link https://valkey.io/commands/script-load/|SCRIPT LOAD} and {@link https://valkey.io/commands/evalsha/|EVALSHA} on valkey.io for details. * * @param script - The Lua script to execute. * @param options - The script option that contains keys and arguments for the script. @@ -2640,7 +3201,7 @@ export class BaseClient { * console.log(result); // Output: ['foo', 'bar'] * ``` */ - public invokeScript( + public async invokeScript( script: Script, option?: ScriptOptions, ): Promise { @@ -2668,9 +3229,49 @@ export class BaseClient { return this.createWritePromise(scriptInvocation); } + /** + * Returns stream entries matching a given range of entry IDs. + * + * @see {@link https://valkey.io/commands/xrange/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param start - The starting stream entry ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.NegativeInfinity` to start with the minimum available ID. + * @param end - The ending stream entry ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.PositiveInfinity` to end with the maximum available ID. + * @param count - An optional argument specifying the maximum count of stream entries to return. + * If `count` is not provided, all stream entries in the range will be returned. + * @returns A map of stream entry ids, to an array of entries, or `null` if `count` is negative. + * + * @example + * ```typescript + * await client.xadd("mystream", [["field1", "value1"]], {id: "0-1"}); + * await client.xadd("mystream", [["field2", "value2"], ["field2", "value3"]], {id: "0-2"}); + * console.log(await client.xrange("mystream", InfBoundary.NegativeInfinity, InfBoundary.PositiveInfinity)); + * // Output: + * // { + * // "0-1": [["field1", "value1"]], + * // "0-2": [["field2", "value2"], ["field2", "value3"]], + * // } // Indicates the stream entry IDs and their associated field-value pairs for all stream entries in "mystream". + * ``` + */ + public async xrange( + key: string, + start: Boundary, + end: Boundary, + count?: number, + ): Promise | null> { + return this.createWritePromise(createXRange(key, start, end, count)); + } + /** Adds members with their scores to the sorted set stored at `key`. * If a member is already a part of the sorted set, its score is updated. - * See https://valkey.io/commands/zadd/ for more details. + * + * @see {@link https://valkey.io/commands/zadd/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param membersScoresMap - A mapping of members to their corresponding scores. @@ -2693,7 +3294,7 @@ export class BaseClient { * console.log(result); // Output: 2 - Updates the scores of two existing members in the sorted set "existing_sorted_set." * ``` */ - public zadd( + public async zadd( key: string, membersScoresMap: Record, options?: ZAddOptions, @@ -2706,7 +3307,8 @@ export class BaseClient { /** Increments the score of member in the sorted set stored at `key` by `increment`. * If `member` does not exist in the sorted set, it is added with `increment` as its score (as if its previous score was 0.0). * If `key` does not exist, a new sorted set with the specified member as its sole member is created. - * See https://valkey.io/commands/zadd/ for more details. + * + * @see {@link https://valkey.io/commands/zadd/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param member - A member in the sorted set to increment. @@ -2729,7 +3331,7 @@ export class BaseClient { * console.log(result); // Output: null - Indicates that the member in the sorted set haven't been updated. * ``` */ - public zaddIncr( + public async zaddIncr( key: string, member: string, increment: number, @@ -2742,7 +3344,8 @@ export class BaseClient { /** Removes the specified members from the sorted set stored at `key`. * Specified members that are not a member of this set are ignored. - * See https://valkey.io/commands/zrem/ for more details. + * + * @see {@link https://valkey.io/commands/zrem/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param members - A list of members to remove from the sorted set. @@ -2763,12 +3366,13 @@ export class BaseClient { * console.log(result); // Output: 0 - Indicates that no members were removed as the sorted set "non_existing_sorted_set" does not exist. * ``` */ - public zrem(key: string, members: string[]): Promise { + public async zrem(key: string, members: string[]): Promise { return this.createWritePromise(createZRem(key, members)); } /** Returns the cardinality (number of elements) of the sorted set stored at `key`. - * See https://valkey.io/commands/zcard/ for more details. + * + * @see {@link https://valkey.io/commands/zcard/|valkey.io} for more details. * * @param key - The key of the sorted set. * @returns The number of elements in the sorted set. @@ -2788,30 +3392,29 @@ export class BaseClient { * console.log(result); // Output: 0 * ``` */ - public zcard(key: string): Promise { + public async zcard(key: string): Promise { return this.createWritePromise(createZCard(key)); } /** * Returns the cardinality of the intersection of the sorted sets specified by `keys`. * - * See https://valkey.io/commands/zintercard/ for more details. - * + * @see {@link https://valkey.io/commands/zintercard/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * * @param keys - The keys of the sorted sets to intersect. * @param limit - An optional argument that can be used to specify a maximum number for the * intersection cardinality. If limit is not supplied, or if it is set to `0`, there will be no limit. * @returns The cardinality of the intersection of the given sorted sets. * - * since - Redis version 7.0.0. - * * @example * ```typescript * const cardinality = await client.zintercard(["key1", "key2"], 10); * console.log(cardinality); // Output: 3 - The intersection of the sorted sets at "key1" and "key2" has a cardinality of 3. * ``` */ - public zintercard(keys: string[], limit?: number): Promise { + public async zintercard(keys: string[], limit?: number): Promise { return this.createWritePromise(createZInterCard(keys, limit)); } @@ -2819,15 +3422,14 @@ export class BaseClient { * Returns the difference between the first sorted set and all the successive sorted sets. * To get the elements with their scores, see {@link zdiffWithScores}. * - * See https://valkey.io/commands/zdiff/ for more details. - * + * @see {@link https://valkey.io/commands/zdiff/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 6.2.0. + * * @param keys - The keys of the sorted sets. * @returns An `array` of elements representing the difference between the sorted sets. * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`. * - * since Valkey version 6.2.0. - * * @example * ```typescript * await client.zadd("zset1", {"member1": 1.0, "member2": 2.0, "member3": 3.0}); @@ -2837,7 +3439,7 @@ export class BaseClient { * console.log(result); // Output: ["member1"] - "member1" is in "zset1" but not "zset2" or "zset3". * ``` */ - public zdiff(keys: string[]): Promise { + public async zdiff(keys: string[]): Promise { return this.createWritePromise(createZDiff(keys)); } @@ -2845,15 +3447,14 @@ export class BaseClient { * Returns the difference between the first sorted set and all the successive sorted sets, with the associated * scores. * - * See https://valkey.io/commands/zdiff/ for more details. - * + * @see {@link https://valkey.io/commands/zdiff/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 6.2.0. + * * @param keys - The keys of the sorted sets. * @returns A map of elements and their scores representing the difference between the sorted sets. * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`. * - * since Valkey version 6.2.0. - * * @example * ```typescript * await client.zadd("zset1", {"member1": 1.0, "member2": 2.0, "member3": 3.0}); @@ -2863,7 +3464,9 @@ export class BaseClient { * console.log(result); // Output: {"member1": 1.0} - "member1" is in "zset1" but not "zset2" or "zset3". * ``` */ - public zdiffWithScores(keys: string[]): Promise> { + public async zdiffWithScores( + keys: string[], + ): Promise> { return this.createWritePromise(createZDiffWithScores(keys)); } @@ -2872,15 +3475,14 @@ export class BaseClient { * the difference as a sorted set to `destination`, overwriting it if it already exists. Non-existent keys are * treated as empty sets. * - * See https://valkey.io/commands/zdiffstore/ for more details. - * + * @see {@link https://valkey.io/commands/zdiffstore/|valkey.io} for more details. * @remarks When in cluster mode, all keys in `keys` and `destination` must map to the same hash slot. + * @remarks Since Valkey version 6.2.0. + * * @param destination - The key for the resulting sorted set. * @param keys - The keys of the sorted sets to compare. * @returns The number of members in the resulting sorted set stored at `destination`. * - * since Valkey version 6.2.0. - * * @example * ```typescript * await client.zadd("zset1", {"member1": 1.0, "member2": 2.0}); @@ -2892,12 +3494,16 @@ export class BaseClient { * console.log(result2); // Output: ["member2"] - "member2" is now stored in "my_sorted_set". * ``` */ - public zdiffstore(destination: string, keys: string[]): Promise { + public async zdiffstore( + destination: string, + keys: string[], + ): Promise { return this.createWritePromise(createZDiffStore(destination, keys)); } /** Returns the score of `member` in the sorted set stored at `key`. - * See https://valkey.io/commands/zscore/ for more details. + * + * @see {@link https://valkey.io/commands/zscore/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param member - The member whose score is to be retrieved. @@ -2926,34 +3532,37 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public zscore(key: string, member: string): Promise { + public async zscore(key: string, member: string): Promise { return this.createWritePromise(createZScore(key, member)); } /** * Returns the scores associated with the specified `members` in the sorted set stored at `key`. * - * See https://valkey.io/commands/zmscore/ for more details. + * @see {@link https://valkey.io/commands/zmscore/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. * * @param key - The key of the sorted set. * @param members - A list of members in the sorted set. * @returns An `array` of scores corresponding to `members`. * If a member does not exist in the sorted set, the corresponding value in the list will be `null`. * - * since Valkey version 6.2.0. - * * @example * ```typescript * const result = await client.zmscore("zset1", ["member1", "non_existent_member", "member2"]); * console.log(result); // Output: [1.0, null, 2.0] - "member1" has a score of 1.0, "non_existent_member" does not exist in the sorted set, and "member2" has a score of 2.0. * ``` */ - public zmscore(key: string, members: string[]): Promise<(number | null)[]> { + public async zmscore( + key: string, + members: string[], + ): Promise<(number | null)[]> { return this.createWritePromise(createZMScore(key, members)); } /** Returns the number of members in the sorted set stored at `key` with scores between `minScore` and `maxScore`. - * See https://valkey.io/commands/zcount/ for more details. + * + * @see {@link https://valkey.io/commands/zcount/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param minScore - The minimum score to count from. Can be positive/negative infinity, or specific score and inclusivity. @@ -2965,7 +3574,7 @@ export class BaseClient { * @example * ```typescript * // Example usage of the zcount method to count members in a sorted set within a score range - * const result = await client.zcount("my_sorted_set", { value: 5.0, isInclusive: true }, InfScoreBoundary.PositiveInfinity); + * const result = await client.zcount("my_sorted_set", { value: 5.0, isInclusive: true }, InfBoundary.PositiveInfinity); * console.log(result); // Output: 2 - Indicates that there are 2 members with scores between 5.0 (inclusive) and +inf in the sorted set "my_sorted_set". * ``` * @@ -2976,10 +3585,10 @@ export class BaseClient { * console.log(result); // Output: 1 - Indicates that there is one member with score between 5.0 (inclusive) and 10.0 (exclusive) in the sorted set "my_sorted_set". * ``` */ - public zcount( + public async zcount( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): Promise { return this.createWritePromise(createZCount(key, minScore, maxScore)); } @@ -2987,15 +3596,16 @@ export class BaseClient { /** Returns the specified range of elements in the sorted set stored at `key`. * ZRANGE can perform different types of range queries: by index (rank), by the score, or by lexicographical order. * - * See https://valkey.io/commands/zrange/ for more details. - * To get the elements with their scores, see `zrangeWithScores`. + * To get the elements with their scores, see {@link zrangeWithScores}. + * + * @see {@link https://valkey.io/commands/zrange/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param rangeQuery - The range query object representing the type of range query to perform. - * For range queries by index (rank), use RangeByIndex. - * For range queries by lexicographical order, use RangeByLex. - * For range queries by score, use RangeByScore. - * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score. + * - For range queries by index (rank), use {@link RangeByIndex}. + * - For range queries by lexicographical order, use {@link RangeByLex}. + * - For range queries by score, use {@link RangeByScore}. + * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score. * @returns A list of elements within the specified range. * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty array. * @@ -3009,14 +3619,14 @@ export class BaseClient { * ```typescript * // Example usage of zrange method to retrieve members within a score range in ascending order * const result = await client.zrange("my_sorted_set", { - * start: InfScoreBoundary.NegativeInfinity, + * start: InfBoundary.NegativeInfinity, * stop: { value: 3, isInclusive: false }, * type: "byScore", * }); * console.log(result); // Output: ['member2', 'member3'] - Returns members with scores within the range of negative infinity to 3, in ascending order. * ``` */ - public zrange( + public async zrange( key: string, rangeQuery: RangeByScore | RangeByLex | RangeByIndex, reverse: boolean = false, @@ -3026,14 +3636,15 @@ export class BaseClient { /** Returns the specified range of elements with their scores in the sorted set stored at `key`. * Similar to ZRANGE but with a WITHSCORE flag. - * See https://valkey.io/commands/zrange/ for more details. + * + * @see {@link https://valkey.io/commands/zrange/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param rangeQuery - The range query object representing the type of range query to perform. - * For range queries by index (rank), use RangeByIndex. - * For range queries by lexicographical order, use RangeByLex. - * For range queries by score, use RangeByScore. - * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score. + * - For range queries by index (rank), use {@link RangeByIndex}. + * - For range queries by lexicographical order, use {@link RangeByLex}. + * - For range queries by score, use {@link RangeByScore}. + * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score. * @returns A map of elements and their scores within the specified range. * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty map. * @@ -3051,14 +3662,14 @@ export class BaseClient { * ```typescript * // Example usage of zrangeWithScores method to retrieve members within a score range with their scores * const result = await client.zrangeWithScores("my_sorted_set", { - * start: InfScoreBoundary.NegativeInfinity, + * start: InfBoundary.NegativeInfinity, * stop: { value: 3, isInclusive: false }, * type: "byScore", * }); * console.log(result); // Output: {'member4': -2.0, 'member7': 1.5} - Returns members with scores within the range of negative infinity to 3, with their scores. * ``` */ - public zrangeWithScores( + public async zrangeWithScores( key: string, rangeQuery: RangeByScore | RangeByLex | RangeByIndex, reverse: boolean = false, @@ -3068,14 +3679,59 @@ export class BaseClient { ); } + /** + * Stores a specified range of elements from the sorted set at `source`, into a new + * sorted set at `destination`. If `destination` doesn't exist, a new sorted + * set is created; if it exists, it's overwritten. + * + * @see {@link https://valkey.io/commands/zrangestore/|valkey.io} for more details. + * @remarks When in cluster mode, `destination` and `source` must map to the same hash slot. + * @remarks Since Valkey version 6.2.0. + * + * @param destination - The key for the destination sorted set. + * @param source - The key of the source sorted set. + * @param rangeQuery - The range query object representing the type of range query to perform. + * - For range queries by index (rank), use {@link RangeByIndex}. + * - For range queries by lexicographical order, use {@link RangeByLex}. + * - For range queries by score, use {@link RangeByScore}. + * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score. + * @returns The number of elements in the resulting sorted set. + * + * @example + * ```typescript + * // Example usage of zrangeStore to retrieve and store all members of a sorted set in ascending order. + * const result = await client.zrangeStore("destination_key", "my_sorted_set", { start: 0, stop: -1 }); + * console.log(result); // Output: 7 - "destination_key" contains a sorted set with the 7 members from "my_sorted_set". + * ``` + * @example + * ```typescript + * // Example usage of zrangeStore method to retrieve members within a score range in ascending order and store in "destination_key" + * const result = await client.zrangeStore("destination_key", "my_sorted_set", { + * start: InfBoundary.NegativeInfinity, + * stop: { value: 3, isInclusive: false }, + * type: "byScore", + * }); + * console.log(result); // Output: 5 - Stores 5 members with scores within the range of negative infinity to 3, in ascending order, in "destination_key". + * ``` + */ + public async zrangeStore( + destination: string, + source: string, + rangeQuery: RangeByScore | RangeByLex | RangeByIndex, + reverse: boolean = false, + ): Promise { + return this.createWritePromise( + createZRangeStore(destination, source, rangeQuery, reverse), + ); + } + /** * Computes the intersection of sorted sets given by the specified `keys` and stores the result in `destination`. * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created. * To get the result directly, see `zinter_withscores`. * - * When in cluster mode, `destination` and all keys in `keys` must map to the same hash slot. - * - * See https://valkey.io/commands/zinterstore/ for more details. + * @see {@link https://valkey.io/commands/zinterstore/|valkey.io} for more details. + * @remarks When in cluster mode, `destination` and all keys in `keys` must map to the same hash slot. * * @param destination - The key of the destination sorted set. * @param keys - The keys of the sorted sets with possible formats: @@ -3095,7 +3751,7 @@ export class BaseClient { * await client.zrange_withscores("my_sorted_set", RangeByIndex(0, -1)) // Output: {'member1': 10.5} - "member1" is now stored in "my_sorted_set" with score of 10.5. * ``` */ - public zinterstore( + public async zinterstore( destination: string, keys: string[] | KeyWeight[], aggregationType?: AggregationType, @@ -3108,7 +3764,7 @@ export class BaseClient { /** * Returns a random member from the sorted set stored at `key`. * - * See https://valkey.io/commands/zrandmember/ for more details. + * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for more details. * * @param keys - The key of the sorted set. * @returns A string representing a random member from the sorted set. @@ -3133,7 +3789,7 @@ export class BaseClient { /** * Returns random members from the sorted set stored at `key`. * - * See https://valkey.io/commands/zrandmember/ for more details. + * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for more details. * * @param keys - The key of the sorted set. * @param count - The number of members to return. @@ -3164,7 +3820,7 @@ export class BaseClient { /** * Returns random members with scores from the sorted set stored at `key`. * - * See https://valkey.io/commands/zrandmember/ for more details. + * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for more details. * * @param keys - The key of the sorted set. * @param count - The number of members to return. @@ -3194,7 +3850,8 @@ export class BaseClient { } /** Returns the length of the string value stored at `key`. - * See https://valkey.io/commands/strlen/ for more details. + * + * @see {@link https://valkey.io/commands/strlen/|valkey.io} for more details. * * @param key - The key to check its length. * @returns - The length of the string value stored at key @@ -3215,12 +3872,13 @@ export class BaseClient { * console.log(len2); // Output: 0 * ``` */ - public strlen(key: string): Promise { + public async strlen(key: string): Promise { return this.createWritePromise(createStrlen(key)); } /** Returns the string representation of the type of the value stored at `key`. - * See https://valkey.io/commands/type/ for more details. + * + * @see {@link https://valkey.io/commands/type/|valkey.io} for more details. * * @param key - The `key` to check its data type. * @returns If the `key` exists, the type of the stored value is returned. Otherwise, a "none" string is returned. @@ -3241,14 +3899,15 @@ export class BaseClient { * console.log(type); // Output: 'list' * ``` */ - public type(key: string): Promise { + public async type(key: string): Promise { return this.createWritePromise(createType(key)); } /** Removes and returns the members with the lowest scores from the sorted set stored at `key`. * If `count` is provided, up to `count` members with the lowest scores are removed and returned. * Otherwise, only one member with the lowest score is removed and returned. - * See https://valkey.io/commands/zpopmin for more details. + * + * @see {@link https://valkey.io/commands/zpopmin/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param count - Specifies the quantity of members to pop. If not specified, pops one member. @@ -3270,17 +3929,46 @@ export class BaseClient { * console.log(result); // Output: {'member3': 7.5 , 'member2': 8.0} - Indicates that 'member3' with a score of 7.5 and 'member2' with a score of 8.0 have been removed from the sorted set. * ``` */ - public zpopmin( + public async zpopmin( key: string, count?: number, ): Promise> { return this.createWritePromise(createZPopMin(key, count)); } + /** + * Blocks the connection until it removes and returns a member with the lowest score from the + * first non-empty sorted set, with the given `key` being checked in the order they + * are provided. + * `BZPOPMIN` is the blocking variant of {@link zpopmin}. + * + * @see {@link https://valkey.io/commands/bzpopmin/|valkey.io} for more details. + * @remarks When in cluster mode, `keys` must map to the same hash slot. + * + * @param keys - The keys of the sorted sets. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of + * `0` will block indefinitely. Since 6.0.0: timeout is interpreted as a double instead of an integer. + * @returns An `array` containing the key where the member was popped out, the member, itself, and the member score. + * If no member could be popped and the `timeout` expired, returns `null`. + * + * @example + * ```typescript + * const data = await client.bzpopmin(["zset1", "zset2"], 0.5); + * console.log(data); // Output: ["zset1", "a", 2]; + * ``` + */ + public async bzpopmin( + keys: string[], + timeout: number, + ): Promise<[string, string, number] | null> { + return this.createWritePromise(createBZPopMin(keys, timeout)); + } + /** Removes and returns the members with the highest scores from the sorted set stored at `key`. * If `count` is provided, up to `count` members with the highest scores are removed and returned. * Otherwise, only one member with the highest score is removed and returned. - * See https://valkey.io/commands/zpopmax for more details. + * + * @see {@link https://valkey.io/commands/zpopmax/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param count - Specifies the quantity of members to pop. If not specified, pops one member. @@ -3302,15 +3990,44 @@ export class BaseClient { * console.log(result); // Output: {'member2': 8.0, 'member3': 7.5} - Indicates that 'member2' with a score of 8.0 and 'member3' with a score of 7.5 have been removed from the sorted set. * ``` */ - public zpopmax( + public async zpopmax( key: string, count?: number, ): Promise> { return this.createWritePromise(createZPopMax(key, count)); } + /** + * Blocks the connection until it removes and returns a member with the highest score from the + * first non-empty sorted set, with the given `key` being checked in the order they + * are provided. + * `BZPOPMAX` is the blocking variant of {@link zpopmax}. + * + * @see {@link https://valkey.io/commands/zpopmax/|valkey.io} for more details. + * @remarks When in cluster mode, `keys` must map to the same hash slot. + * + * @param keys - The keys of the sorted sets. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of + * `0` will block indefinitely. Since 6.0.0: timeout is interpreted as a double instead of an integer. + * @returns An `array` containing the key where the member was popped out, the member, itself, and the member score. + * If no member could be popped and the `timeout` expired, returns `null`. + * + * @example + * ```typescript + * const data = await client.bzpopmax(["zset1", "zset2"], 0.5); + * console.log(data); // Output: ["zset1", "c", 2]; + * ``` + */ + public async bzpopmax( + keys: string[], + timeout: number, + ): Promise<[string, string, number] | null> { + return this.createWritePromise(createBZPopMax(keys, timeout)); + } + /** Returns the remaining time to live of `key` that has a timeout, in milliseconds. - * See https://valkey.io/commands/pttl for more details. + * + * @see {@link https://valkey.io/commands/pttl/|valkey.io} for more details. * * @param key - The key to return its timeout. * @returns TTL in milliseconds. -2 if `key` does not exist, -1 if `key` exists but has no associated expire. @@ -3336,14 +4053,15 @@ export class BaseClient { * console.log(result); // Output: -1 - Indicates that the key "key" has no associated expire. * ``` */ - public pttl(key: string): Promise { + public async pttl(key: string): Promise { return this.createWritePromise(createPTTL(key)); } /** Removes all elements in the sorted set stored at `key` with rank between `start` and `end`. * Both `start` and `end` are zero-based indexes with 0 being the element with the lowest score. * These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score. - * See https://valkey.io/commands/zremrangebyrank/ for more details. + * + * @see {@link https://valkey.io/commands/zremrangebyrank/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param start - The starting point of the range. @@ -3360,7 +4078,7 @@ export class BaseClient { * console.log(result); // Output: 3 - Indicates that three elements have been removed from the sorted set "my_sorted_set" between ranks 0 and 2. * ``` */ - public zremRangeByRank( + public async zremRangeByRank( key: string, start: number, end: number, @@ -3371,7 +4089,7 @@ export class BaseClient { /** * Removes all elements in the sorted set stored at `key` with lexicographical order between `minLex` and `maxLex`. * - * See https://valkey.io/commands/zremrangebylex/ for more details. + * @see {@link https://valkey.io/commands/zremrangebylex/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param minLex - The minimum lex to count from. Can be positive/negative infinity, or a specific lex and inclusivity. @@ -3390,14 +4108,14 @@ export class BaseClient { * @example * ```typescript * // Example usage of zremRangeByLex method when the sorted set does not exist - * const result = await client.zremRangeByLex("non_existing_sorted_set", InfScoreBoundary.NegativeInfinity, { value: "e" }); + * const result = await client.zremRangeByLex("non_existing_sorted_set", InfBoundary.NegativeInfinity, { value: "e" }); * console.log(result); // Output: 0 - Indicates that no elements were removed. * ``` */ - public zremRangeByLex( + public async zremRangeByLex( key: string, - minLex: ScoreBoundary, - maxLex: ScoreBoundary, + minLex: Boundary, + maxLex: Boundary, ): Promise { return this.createWritePromise( createZRemRangeByLex(key, minLex, maxLex), @@ -3405,7 +4123,8 @@ export class BaseClient { } /** Removes all elements in the sorted set stored at `key` with a score between `minScore` and `maxScore`. - * See https://valkey.io/commands/zremrangebyscore/ for more details. + * + * @see {@link https://valkey.io/commands/zremrangebyscore/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param minScore - The minimum score to remove from. Can be positive/negative infinity, or specific score and inclusivity. @@ -3417,7 +4136,7 @@ export class BaseClient { * @example * ```typescript * // Example usage of zremRangeByScore method to remove members from a sorted set based on score range - * const result = await client.zremRangeByScore("my_sorted_set", { value: 5.0, isInclusive: true }, InfScoreBoundary.PositiveInfinity); + * const result = await client.zremRangeByScore("my_sorted_set", { value: 5.0, isInclusive: true }, InfBoundary.PositiveInfinity); * console.log(result); // Output: 2 - Indicates that 2 members with scores between 5.0 (inclusive) and +inf have been removed from the sorted set "my_sorted_set". * ``` * @@ -3428,10 +4147,10 @@ export class BaseClient { * console.log(result); // Output: 0 - Indicates that no members were removed as the sorted set "non_existing_sorted_set" does not exist. * ``` */ - public zremRangeByScore( + public async zremRangeByScore( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): Promise { return this.createWritePromise( createZRemRangeByScore(key, minScore, maxScore), @@ -3441,7 +4160,7 @@ export class BaseClient { /** * Returns the number of members in the sorted set stored at 'key' with scores between 'minLex' and 'maxLex'. * - * See https://valkey.io/commands/zlexcount/ for more details. + * @see {@link https://valkey.io/commands/zlexcount/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param minLex - The minimum lex to count from. Can be positive/negative infinity, or a specific lex and inclusivity. @@ -3452,7 +4171,7 @@ export class BaseClient { * * @example * ```typescript - * const result = await client.zlexcount("my_sorted_set", {value: "c"}, InfScoreBoundary.PositiveInfinity); + * const result = await client.zlexcount("my_sorted_set", {value: "c"}, InfBoundary.PositiveInfinity); * console.log(result); // Output: 2 - Indicates that there are 2 members with lex scores between "c" (inclusive) and positive infinity in the sorted set "my_sorted_set". * ``` * @@ -3464,15 +4183,16 @@ export class BaseClient { */ public async zlexcount( key: string, - minLex: ScoreBoundary, - maxLex: ScoreBoundary, + minLex: Boundary, + maxLex: Boundary, ): Promise { return this.createWritePromise(createZLexCount(key, minLex, maxLex)); } /** Returns the rank of `member` in the sorted set stored at `key`, with scores ordered from low to high. - * See https://valkey.io/commands/zrank for more details. - * To get the rank of `member` with its score, see `zrankWithScore`. + * To get the rank of `member` with its score, see {@link zrankWithScore}. + * + * @see {@link https://valkey.io/commands/zrank/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param member - The member whose rank is to be retrieved. @@ -3493,20 +4213,20 @@ export class BaseClient { * console.log(result); // Output: null - Indicates that "non_existing_member" is not present in the sorted set "my_sorted_set". * ``` */ - public zrank(key: string, member: string): Promise { + public async zrank(key: string, member: string): Promise { return this.createWritePromise(createZRank(key, member)); } /** Returns the rank of `member` in the sorted set stored at `key` with its score, where scores are ordered from the lowest to highest. - * See https://valkey.io/commands/zrank for more details. + * + * @see {@link https://valkey.io/commands/zrank/|valkey.io} for more details. + * @remarks Since Valkey version 7.2.0. * * @param key - The key of the sorted set. * @param member - The member whose rank is to be retrieved. * @returns A list containing the rank and score of `member` in the sorted set. * If `key` doesn't exist, or if `member` is not present in the set, null will be returned. * - * since - Redis version 7.2.0. - * * @example * ```typescript * // Example usage of zrank_withscore method to retrieve the rank and score of a member in a sorted set @@ -3521,7 +4241,7 @@ export class BaseClient { * console.log(result); // Output: null - Indicates that "non_existing_member" is not present in the sorted set "my_sorted_set". * ``` */ - public zrankWithScore( + public async zrankWithScore( key: string, member: string, ): Promise { @@ -3533,7 +4253,7 @@ export class BaseClient { * scores are ordered from the highest to lowest, starting from 0. * To get the rank of `member` with its score, see {@link zrevrankWithScore}. * - * See https://valkey.io/commands/zrevrank/ for more details. + * @see {@link https://valkey.io/commands/zrevrank/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param member - The member whose rank is to be retrieved. @@ -3546,7 +4266,7 @@ export class BaseClient { * console.log(result); // Output: 1 - Indicates that "member2" has the second-highest score in the sorted set "my_sorted_set". * ``` */ - public zrevrank(key: string, member: string): Promise { + public async zrevrank(key: string, member: string): Promise { return this.createWritePromise(createZRevRank(key, member)); } @@ -3554,7 +4274,8 @@ export class BaseClient { * Returns the rank of `member` in the sorted set stored at `key` with its * score, where scores are ordered from the highest to lowest, starting from 0. * - * See https://valkey.io/commands/zrevrank/ for more details. + * @see {@link https://valkey.io/commands/zrevrank/|valkey.io} for more details. + * @remarks Since Valkey version 7.2.0. * * @param key - The key of the sorted set. * @param member - The member whose rank is to be retrieved. @@ -3562,15 +4283,13 @@ export class BaseClient { * are ordered from high to low based on scores. * If `key` doesn't exist, or if `member` is not present in the set, `null` will be returned. * - * since - Valkey version 7.2.0. - * * @example * ```typescript * const result = await client.zrevankWithScore("my_sorted_set", "member2"); * console.log(result); // Output: [1, 6.0] - Indicates that "member2" with score 6.0 has the second-highest score in the sorted set "my_sorted_set". * ``` */ - public zrevrankWithScore( + public async zrevrankWithScore( key: string, member: string, ): Promise<(number[] | null)[]> { @@ -3579,14 +4298,15 @@ export class BaseClient { /** * Adds an entry to the specified stream stored at `key`. If the `key` doesn't exist, the stream is created. - * See https://valkey.io/commands/xadd/ for more details. + * + * @see {@link https://valkey.io/commands/xadd/|valkey.io} for more details. * * @param key - The key of the stream. * @param values - field-value pairs to be added to the entry. * @param options - options detailing how to add to the stream. * @returns The id of the added entry, or `null` if `options.makeStream` is set to `false` and no stream with the matching `key` exists. */ - public xadd( + public async xadd( key: string, values: [string, string][], options?: StreamAddOptions, @@ -3597,7 +4317,7 @@ export class BaseClient { /** * Removes the specified entries by id from a stream, and returns the number of entries deleted. * - * See https://valkey.io/commands/xdel for more details. + * @see {@link https://valkey.io/commands/xdel/|valkey.io} for more details. * * @param key - The key of the stream. * @param ids - An array of entry ids. @@ -3610,25 +4330,30 @@ export class BaseClient { * // Output is 2 since the stream marked 2 entries as deleted. * ``` */ - public xdel(key: string, ids: string[]): Promise { + public async xdel(key: string, ids: string[]): Promise { return this.createWritePromise(createXDel(key, ids)); } /** * Trims the stream stored at `key` by evicting older entries. - * See https://valkey.io/commands/xtrim/ for more details. + * + * @see {@link https://valkey.io/commands/xtrim/|valkey.io} for more details. * * @param key - the key of the stream * @param options - options detailing how to trim the stream. * @returns The number of entries deleted from the stream. If `key` doesn't exist, 0 is returned. */ - public xtrim(key: string, options: StreamTrimOptions): Promise { + public async xtrim( + key: string, + options: StreamTrimOptions, + ): Promise { return this.createWritePromise(createXTrim(key, options)); } /** * Reads entries from the given streams. - * See https://valkey.io/commands/xread/ for more details. + * + * @see {@link https://valkey.io/commands/xread/|valkey.io} for more details. * * @param keys_and_ids - pairs of keys and entry ids to read from. A pair is composed of a stream's key and the id of the entry after which the stream will be read. * @param options - options detailing how to read the stream. @@ -3636,28 +4361,30 @@ export class BaseClient { * @example * ```typescript * const streamResults = await client.xread({"my_stream": "0-0", "writers": "0-0"}); - * console.log(result); // Output: { - * // "my_stream": { - * // "1526984818136-0": [["duration", "1532"], ["event-id", "5"], ["user-id", "7782813"]], - * // "1526999352406-0": [["duration", "812"], ["event-id", "9"], ["user-id", "388234"]], - * // }, "writers": { - * // "1526985676425-0": [["name", "Virginia"], ["surname", "Woolf"]], - * // "1526985685298-0": [["name", "Jane"], ["surname", "Austen"]], - * // } - * // } - * ``` - */ - public xread( + * console.log(result); // Output: + * // { + * // "my_stream": { + * // "1526984818136-0": [["duration", "1532"], ["event-id", "5"], ["user-id", "7782813"]], + * // "1526999352406-0": [["duration", "812"], ["event-id", "9"], ["user-id", "388234"]], + * // }, + * // "writers": { + * // "1526985676425-0": [["name", "Virginia"], ["surname", "Woolf"]], + * // "1526985685298-0": [["name", "Jane"], ["surname", "Austen"]], + * // } + * // } + * ``` + */ + public async xread( keys_and_ids: Record, options?: StreamReadOptions, - ): Promise>> { + ): Promise>> { return this.createWritePromise(createXRead(keys_and_ids, options)); } /** * Returns the number of entries in the stream stored at `key`. * - * See https://valkey.io/commands/xlen/ for more details. + * @see {@link https://valkey.io/commands/xlen/|valkey.io} for more details. * * @param key - The key of the stream. * @returns The number of entries in the stream. If `key` does not exist, returns `0`. @@ -3668,14 +4395,343 @@ export class BaseClient { * console.log(numEntries); // Output: 2 - "my_stream" contains 2 entries. * ``` */ - public xlen(key: string): Promise { + public async xlen(key: string): Promise { return this.createWritePromise(createXLen(key)); } + /** + * Returns stream message summary information for pending messages matching a given range of IDs. + * + * @see {@link https://valkey.io/commands/xpending/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @returns An `array` that includes the summary of the pending messages. See example for more details. + * @example + * ```typescript + * console.log(await client.xpending("my_stream", "my_group")); // Output: + * // [ + * // 42, // The total number of pending messages + * // "1722643465939-0", // The smallest ID among the pending messages + * // "1722643484626-0", // The greatest ID among the pending messages + * // [ // A 2D-`array` of every consumer in the group + * // [ "consumer1", "10" ], // with at least one pending message, and the + * // [ "consumer2", "32" ], // number of pending messages it has + * // ] + * // ] + * ``` + */ + public async xpending( + key: string, + group: string, + ): Promise<[number, string, string, [string, number][]]> { + return this.createWritePromise(createXPending(key, group)); + } + + /** + * Returns an extended form of stream message information for pending messages matching a given range of IDs. + * + * @see {@link https://valkey.io/commands/xpending/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @param options - Additional options to filter entries, see {@link StreamPendingOptions}. + * @returns A 2D-`array` of 4-tuples containing extended message information. See example for more details. + * + * @example + * ```typescript + * console.log(await client.xpending("my_stream", "my_group"), { + * start: { value: "0-1", isInclusive: true }, + * end: InfBoundary.PositiveInfinity, + * count: 2, + * consumer: "consumer1" + * }); // Output: + * // [ + * // [ + * // "1722643465939-0", // The ID of the message + * // "consumer1", // The name of the consumer that fetched the message and has still to acknowledge it + * // 174431, // The number of milliseconds that elapsed since the last time this message was delivered to this consumer + * // 1 // The number of times this message was delivered + * // ], + * // [ + * // "1722643484626-0", + * // "consumer1", + * // 202231, + * // 1 + * // ] + * // ] + * ``` + */ + public async xpendingWithOptions( + key: string, + group: string, + options: StreamPendingOptions, + ): Promise<[string, string, number, number][]> { + return this.createWritePromise(createXPending(key, group, options)); + } + + /** + * Returns the list of all consumers and their attributes for the given consumer group of the + * stream stored at `key`. + * + * @see {@link https://valkey.io/commands/xinfo-consumers/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @returns An `Array` of `Records`, where each mapping contains the attributes + * of a consumer for the given consumer group of the stream at `key`. + * + * @example + * ```typescript + * const result = await client.xinfoConsumers("my_stream", "my_group"); + * console.log(result); // Output: + * // [ + * // { + * // "name": "Alice", + * // "pending": 1, + * // "idle": 9104628, + * // "inactive": 18104698 // Added in 7.2.0 + * // }, + * // ... + * // ] + * ``` + */ + public async xinfoConsumers( + key: string, + group: string, + ): Promise[]> { + return this.createWritePromise(createXInfoConsumers(key, group)); + } + + /** + * Returns the list of all consumer groups and their attributes for the stream stored at `key`. + * + * @see {@link https://valkey.io/commands/xinfo-groups/|valkey.io} for details. + * + * @param key - The key of the stream. + * @returns An array of maps, where each mapping represents the + * attributes of a consumer group for the stream at `key`. + * @example + * ```typescript + *
{@code
+     * const result = await client.xinfoGroups("my_stream");
+     * console.log(result); // Output:
+     * // [
+     * //     {
+     * //         "name": "mygroup",
+     * //         "consumers": 2,
+     * //         "pending": 2,
+     * //         "last-delivered-id": "1638126030001-0",
+     * //         "entries-read": 2,                       // Added in version 7.0.0
+     * //         "lag": 0                                 // Added in version 7.0.0
+     * //     },
+     * //     {
+     * //         "name": "some-other-group",
+     * //         "consumers": 1,
+     * //         "pending": 0,
+     * //         "last-delivered-id": "0-0",
+     * //         "entries-read": null,                    // Added in version 7.0.0
+     * //         "lag": 1                                 // Added in version 7.0.0
+     * //     }
+     * // ]
+     * ```
+     */
+    public async xinfoGroups(
+        key: string,
+    ): Promise[]> {
+        return this.createWritePromise(createXInfoGroups(key));
+    }
+
+    /**
+     * Changes the ownership of a pending message.
+     *
+     * @see {@link https://valkey.io/commands/xclaim/|valkey.io} for more details.
+     *
+     * @param key - The key of the stream.
+     * @param group - The consumer group name.
+     * @param consumer - The group consumer.
+     * @param minIdleTime - The minimum idle time for the message to be claimed.
+     * @param ids - An array of entry ids.
+     * @param options - (Optional) Stream claim options {@link StreamClaimOptions}.
+     * @returns A `Record` of message entries that are claimed by the consumer.
+     *
+     * @example
+     * ```typescript
+     * const result = await client.xclaim("myStream", "myGroup", "myConsumer", 42,
+     *     ["1-0", "2-0", "3-0"], { idle: 500, retryCount: 3, isForce: true });
+     * console.log(result); // Output:
+     * // {
+     * //     "2-0": [["duration", "1532"], ["event-id", "5"], ["user-id", "7782813"]]
+     * // }
+     * ```
+     */
+    public async xclaim(
+        key: string,
+        group: string,
+        consumer: string,
+        minIdleTime: number,
+        ids: string[],
+        options?: StreamClaimOptions,
+    ): Promise> {
+        return this.createWritePromise(
+            createXClaim(key, group, consumer, minIdleTime, ids, options),
+        );
+    }
+
+    /**
+     * Transfers ownership of pending stream entries that match the specified criteria.
+     *
+     * @see {@link https://valkey.io/commands/xautoclaim/|valkey.io} for more details.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param key - The key of the stream.
+     * @param group - The consumer group name.
+     * @param consumer - The group consumer.
+     * @param minIdleTime - The minimum idle time for the message to be claimed.
+     * @param start - Filters the claimed entries to those that have an ID equal or greater than the
+     *     specified value.
+     * @param count - (Optional) Limits the number of claimed entries to the specified value.
+     * @returns A `tuple` containing the following elements:
+     *   - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is
+     *     equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if
+     *     the entire stream was scanned.
+     *   - A `Record` of the claimed entries.
+     *   - If you are using Valkey 7.0.0 or above, the response list will also include a list containing
+     *     the message IDs that were in the Pending Entries List but no longer exist in the stream.
+     *     These IDs are deleted from the Pending Entries List.
+     *
+     * @example
+     * ```typescript
+     * const result = await client.xautoclaim("myStream", "myGroup", "myConsumer", 42, "0-0", 25);
+     * console.log(result); // Output:
+     * // [
+     * //     "1609338788321-0",                // value to be used as `start` argument
+     * //                                       // for the next `xautoclaim` call
+     * //     {
+     * //         "1609338752495-0": [          // claimed entries
+     * //             ["field 1", "value 1"],
+     * //             ["field 2", "value 2"]
+     * //         ]
+     * //     },
+     * //     [
+     * //         "1594324506465-0",            // array of IDs of deleted messages,
+     * //         "1594568784150-0"             // included in the response only on valkey 7.0.0 and above
+     * //     ]
+     * // ]
+     * ```
+     */
+    public async xautoclaim(
+        key: string,
+        group: string,
+        consumer: string,
+        minIdleTime: number,
+        start: string,
+        count?: number,
+    ): Promise<[string, Record, string[]?]> {
+        return this.createWritePromise(
+            createXAutoClaim(key, group, consumer, minIdleTime, start, count),
+        );
+    }
+
+    /**
+     * Transfers ownership of pending stream entries that match the specified criteria.
+     *
+     * @see {@link https://valkey.io/commands/xautoclaim/|valkey.io} for more details.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param key - The key of the stream.
+     * @param group - The consumer group name.
+     * @param consumer - The group consumer.
+     * @param minIdleTime - The minimum idle time for the message to be claimed.
+     * @param start - Filters the claimed entries to those that have an ID equal or greater than the
+     *     specified value.
+     * @param count - (Optional) Limits the number of claimed entries to the specified value.
+     * @returns An `array` containing the following elements:
+     *   - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is
+     *     equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if
+     *     the entire stream was scanned.
+     *   - A list of the IDs for the claimed entries.
+     *   - If you are using Valkey 7.0.0 or above, the response list will also include a list containing
+     *     the message IDs that were in the Pending Entries List but no longer exist in the stream.
+     *     These IDs are deleted from the Pending Entries List.
+     *
+     * @example
+     * ```typescript
+     * const result = await client.xautoclaim("myStream", "myGroup", "myConsumer", 42, "0-0", 25);
+     * console.log(result); // Output:
+     * // [
+     * //     "1609338788321-0",                // value to be used as `start` argument
+     * //                                       // for the next `xautoclaim` call
+     * //     [
+     * //         "1609338752495-0",            // claimed entries
+     * //         "1609338752495-1",
+     * //     ],
+     * //     [
+     * //         "1594324506465-0",            // array of IDs of deleted messages,
+     * //         "1594568784150-0"             // included in the response only on valkey 7.0.0 and above
+     * //     ]
+     * // ]
+     * ```
+     */
+    public async xautoclaimJustId(
+        key: string,
+        group: string,
+        consumer: string,
+        minIdleTime: number,
+        start: string,
+        count?: number,
+    ): Promise<[string, string[], string[]?]> {
+        return this.createWritePromise(
+            createXAutoClaim(
+                key,
+                group,
+                consumer,
+                minIdleTime,
+                start,
+                count,
+                true,
+            ),
+        );
+    }
+
+    /**
+     * Changes the ownership of a pending message. This function returns an `array` with
+     * only the message/entry IDs, and is equivalent to using `JUSTID` in the Valkey API.
+     *
+     * @see {@link https://valkey.io/commands/xclaim/|valkey.io} for more details.
+     *
+     * @param key - The key of the stream.
+     * @param group - The consumer group name.
+     * @param consumer - The group consumer.
+     * @param minIdleTime - The minimum idle time for the message to be claimed.
+     * @param ids - An array of entry ids.
+     * @param options - (Optional) Stream claim options {@link StreamClaimOptions}.
+     * @returns An `array` of message ids claimed by the consumer.
+     *
+     * @example
+     * ```typescript
+     * const result = await client.xclaimJustId("my_stream", "my_group", "my_consumer", 42,
+     *     ["1-0", "2-0", "3-0"], { idle: 500, retryCount: 3, isForce: true });
+     * console.log(result); // Output: [ "2-0", "3-0" ]
+     * ```
+     */
+    public async xclaimJustId(
+        key: string,
+        group: string,
+        consumer: string,
+        minIdleTime: number,
+        ids: string[],
+        options?: StreamClaimOptions,
+    ): Promise {
+        return this.createWritePromise(
+            createXClaim(key, group, consumer, minIdleTime, ids, options, true),
+        );
+    }
+
     /**
      * Creates a new consumer group uniquely identified by `groupname` for the stream stored at `key`.
      *
-     * See https://valkey.io/commands/xgroup-create/ for more details.
+     * @see {@link https://valkey.io/commands/xgroup-create/|valkey.io} for more details.
      *
      * @param key - The key of the stream.
      * @param groupName - The newly created consumer group name.
@@ -3703,7 +4759,7 @@ export class BaseClient {
     /**
      * Destroys the consumer group `groupname` for the stream stored at `key`.
      *
-     * See https://valkey.io/commands/xgroup-destroy/ for more details.
+     * @see {@link https://valkey.io/commands/xgroup-destroy/|valkey.io} for more details.
      *
      * @param key - The key of the stream.
      * @param groupname - The newly created consumer group name.
@@ -3722,6 +4778,131 @@ export class BaseClient {
         return this.createWritePromise(createXGroupDestroy(key, groupName));
     }
 
+    /**
+     * Returns information about the stream stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/xinfo-stream/|valkey.io} for more details.
+     *
+     * @param key - The key of the stream.
+     * @param fullOptions - If `true`, returns verbose information with a limit of the first 10 PEL entries.
+     * If `number` is specified, returns verbose information limiting the returned PEL entries.
+     * If `0` is specified, returns verbose information with no limit.
+     * @returns A {@link ReturnTypeXinfoStream} of detailed stream information for the given `key`. See
+     *     the example for a sample response.
+     *
+     * @example
+     * ```typescript
+     * const infoResult = await client.xinfoStream("my_stream");
+     * console.log(infoResult);
+     * // Output: {
+     * //   length: 2,
+     * //   'radix-tree-keys': 1,
+     * //   'radix-tree-nodes': 2,
+     * //   'last-generated-id': '1719877599564-1',
+     * //   'max-deleted-entry-id': '0-0',
+     * //   'entries-added': 2,
+     * //   'recorded-first-entry-id': '1719877599564-0',
+     * //   'first-entry': [ '1719877599564-0', ['some_field", "some_value', ...] ],
+     * //   'last-entry': [ '1719877599564-0', ['some_field", "some_value', ...] ],
+     * //   groups: 1,
+     * // }
+     * ```
+     *
+     * @example
+     * ```typescript
+     * const infoResult = await client.xinfoStream("my_stream", true); // default limit of 10 entries
+     * const infoResult = await client.xinfoStream("my_stream", 15); // limit of 15 entries
+     * console.log(infoResult);
+     * // Output: {
+     * //   length: 2,
+     * //   'radix-tree-keys': 1,
+     * //   'radix-tree-nodes': 2,
+     * //   'last-generated-id': '1719877599564-1',
+     * //   'max-deleted-entry-id': '0-0',
+     * //   'entries-added': 2,
+     * //   'recorded-first-entry-id': '1719877599564-0',
+     * //   entries: [ [ '1719877599564-0', ['some_field", "some_value', ...] ] ],
+     * //   groups: [ {
+     * //     name: 'group',
+     * //     'last-delivered-id': '1719877599564-0',
+     * //     'entries-read': 1,
+     * //     lag: 1,
+     * //     'pel-count': 1,
+     * //     pending: [ [ '1719877599564-0', 'consumer', 1722624726802, 1 ] ],
+     * //     consumers: [ {
+     * //         name: 'consumer',
+     * //         'seen-time': 1722624726802,
+     * //         'active-time': 1722624726802,
+     * //         'pel-count': 1,
+     * //         pending: [ [ '1719877599564-0', 'consumer', 1722624726802, 1 ] ],
+     * //         }
+     * //       ]
+     * //     }
+     * //   ]
+     * // }
+     * ```
+     */
+    public async xinfoStream(
+        key: string,
+        fullOptions?: boolean | number,
+    ): Promise {
+        return this.createWritePromise(
+            createXInfoStream(key, fullOptions ?? false),
+        );
+    }
+
+    /**
+     * Creates a consumer named `consumerName` in the consumer group `groupName` for the stream stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/xgroup-createconsumer/|valkey.io} for more details.
+     *
+     * @param key - The key of the stream.
+     * @param groupName - The consumer group name.
+     * @param consumerName - The newly created consumer.
+     * @returns `true` if the consumer is created. Otherwise, returns `false`.
+     *
+     * @example
+     * ```typescript
+     * // The consumer "myconsumer" was created in consumer group "mygroup" for the stream "mystream".
+     * console.log(await client.xgroupCreateConsumer("mystream", "mygroup", "myconsumer")); // Output is true
+     * ```
+     */
+    public async xgroupCreateConsumer(
+        key: string,
+        groupName: string,
+        consumerName: string,
+    ): Promise {
+        return this.createWritePromise(
+            createXGroupCreateConsumer(key, groupName, consumerName),
+        );
+    }
+
+    /**
+     * Deletes a consumer named `consumerName` in the consumer group `groupName` for the stream stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/xgroup-delconsumer/|valkey.io} for more details.
+     *
+     * @param key - The key of the stream.
+     * @param groupName - The consumer group name.
+     * @param consumerName - The consumer to delete.
+     * @returns The number of pending messages the `consumer` had before it was deleted.
+     *
+     * * @example
+     * ```typescript
+     * // Consumer "myconsumer" was deleted, and had 5 pending messages unclaimed.
+     * console.log(await client.xgroupDelConsumer("mystream", "mygroup", "myconsumer")); // Output is 5
+     * ```
+     */
+    public async xgroupDelConsumer(
+        key: string,
+        groupName: string,
+        consumerName: string,
+    ): Promise {
+        return this.createWritePromise(
+            createXGroupDelConsumer(key, groupName, consumerName),
+        );
+    }
+
     private readonly MAP_READ_FROM_STRATEGY: Record<
         ReadFrom,
         connection_request.ReadFrom
@@ -3734,7 +4915,8 @@ export class BaseClient {
      * The index is zero-based, so 0 means the first element, 1 the second element and so on.
      * Negative indices can be used to designate elements starting at the tail of the list.
      * Here, -1 means the last element, -2 means the penultimate and so forth.
-     * See https://valkey.io/commands/lindex/ for more details.
+     *
+     * @see {@link https://valkey.io/commands/lindex/|valkey.io} for more details.
      *
      * @param key - The `key` of the list.
      * @param index - The `index` of the element in the list to retrieve.
@@ -3755,14 +4937,14 @@ export class BaseClient {
      * console.log(result); // Output: 'value3' - Returns the last element in the list stored at 'my_list'.
      * ```
      */
-    public lindex(key: string, index: number): Promise {
+    public async lindex(key: string, index: number): Promise {
         return this.createWritePromise(createLIndex(key, index));
     }
 
     /**
      * Inserts `element` in the list at `key` either before or after the `pivot`.
      *
-     * See https://valkey.io/commands/linsert/ for more details.
+     * @see {@link https://valkey.io/commands/linsert/|valkey.io} for more details.
      *
      * @param key - The key of the list.
      * @param position - The relative position to insert into - either `InsertPosition.Before` or
@@ -3779,7 +4961,7 @@ export class BaseClient {
      * console.log(length); // Output: 2 - The list has a length of 2 after performing the insert.
      * ```
      */
-    public linsert(
+    public async linsert(
         key: string,
         position: InsertPosition,
         pivot: string,
@@ -3792,7 +4974,8 @@ export class BaseClient {
 
     /** Remove the existing timeout on `key`, turning the key from volatile (a key with an expire set) to
      * persistent (a key that will never expire as no timeout is associated).
-     * See https://valkey.io/commands/persist/ for more details.
+     *
+     * @see {@link https://valkey.io/commands/persist/|valkey.io} for more details.
      *
      * @param key - The key to remove the existing timeout on.
      * @returns `false` if `key` does not exist or does not have an associated timeout, `true` if the timeout has been removed.
@@ -3804,16 +4987,17 @@ export class BaseClient {
      * console.log(result); // Output: true - Indicates that the timeout associated with the key "my_key" was successfully removed.
      * ```
      */
-    public persist(key: string): Promise {
+    public async persist(key: string): Promise {
         return this.createWritePromise(createPersist(key));
     }
 
     /**
      * Renames `key` to `newkey`.
      * If `newkey` already exists it is overwritten.
-     * See https://valkey.io/commands/rename/ for more details.
      *
+     * @see {@link https://valkey.io/commands/rename/|valkey.io} for more details.
      * @remarks When in cluster mode, `key` and `newKey` must map to the same hash slot.
+     *
      * @param key - The key to rename.
      * @param newKey - The new name of the key.
      * @returns - If the `key` was successfully renamed, return "OK". If `key` does not exist, an error is thrown.
@@ -3826,15 +5010,16 @@ export class BaseClient {
      * console.log(result); // Output: OK - Indicates successful renaming of the key "old_key" to "new_key".
      * ```
      */
-    public rename(key: string, newKey: string): Promise<"OK"> {
+    public async rename(key: string, newKey: string): Promise<"OK"> {
         return this.createWritePromise(createRename(key, newKey));
     }
 
     /**
      * Renames `key` to `newkey` if `newkey` does not yet exist.
-     * See https://valkey.io/commands/renamenx/ for more details.
      *
+     * @see {@link https://valkey.io/commands/renamenx/|valkey.io} for more details.
      * @remarks When in cluster mode, `key` and `newKey` must map to the same hash slot.
+     *
      * @param key - The key to rename.
      * @param newKey - The new name of the key.
      * @returns - If the `key` was successfully renamed, returns `true`. Otherwise, returns `false`.
@@ -3848,7 +5033,7 @@ export class BaseClient {
      * console.log(result); // Output: true - Indicates successful renaming of the key "old_key" to "new_key".
      * ```
      */
-    public renamenx(key: string, newKey: string): Promise {
+    public async renamenx(key: string, newKey: string): Promise {
         return this.createWritePromise(createRenameNX(key, newKey));
     }
 
@@ -3856,11 +5041,11 @@ export class BaseClient {
      * Pop an element from the tail of the first list that is non-empty,
      * with the given `keys` being checked in the order that they are given.
      * Blocks the connection when there are no elements to pop from any of the given lists.
-     * See https://valkey.io/commands/brpop/ for more details.
      *
-     * @remarks
-     * 1. When in cluster mode, all `keys` must map to the same hash slot.
-     * 2. `BRPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices.
+     * @see {@link https://valkey.io/commands/brpop/|valkey.io} for more details.
+     * @remarks When in cluster mode, all `keys` must map to the same hash slot.
+     * @remarks `BRPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices.
+     *
      * @param keys - The `keys` of the lists to pop from.
      * @param timeout - The `timeout` in seconds.
      * @returns - An `array` containing the `key` from which the element was popped and the value of the popped element,
@@ -3873,7 +5058,7 @@ export class BaseClient {
      * console.log(result); // Output: ["list1", "element"] - Indicates an element "element" was popped from "list1".
      * ```
      */
-    public brpop(
+    public async brpop(
         keys: string[],
         timeout: number,
     ): Promise<[string, string] | null> {
@@ -3884,11 +5069,11 @@ export class BaseClient {
      * Pop an element from the head of the first list that is non-empty,
      * with the given `keys` being checked in the order that they are given.
      * Blocks the connection when there are no elements to pop from any of the given lists.
-     * See https://valkey.io/commands/blpop/ for more details.
      *
-     * @remarks
-     * 1. When in cluster mode, all `keys` must map to the same hash slot.
-     * 2. `BLPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices.
+     * @see {@link https://valkey.io/commands/blpop/|valkey.io} for more details.
+     * @remarks When in cluster mode, all `keys` must map to the same hash slot.
+     * @remarks `BLPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices.
+     *
      * @param keys - The `keys` of the lists to pop from.
      * @param timeout - The `timeout` in seconds.
      * @returns - An `array` containing the `key` from which the element was popped and the value of the popped element,
@@ -3900,7 +5085,7 @@ export class BaseClient {
      * console.log(result); // Output: ['list1', 'element']
      * ```
      */
-    public blpop(
+    public async blpop(
         keys: string[],
         timeout: number,
     ): Promise<[string, string] | null> {
@@ -3911,7 +5096,7 @@ export class BaseClient {
      * Creates a new structure if the `key` does not exist.
      * When no elements are provided, and `key` exists and is a HyperLogLog, then no operation is performed.
      *
-     * See https://valkey.io/commands/pfadd/ for more details.
+     * @see {@link https://valkey.io/commands/pfadd/|valkey.io} for more details.
      *
      * @param key - The key of the HyperLogLog data structure to add elements into.
      * @param elements - An array of members to add to the HyperLogLog stored at `key`.
@@ -3925,16 +5110,16 @@ export class BaseClient {
      * console.log(result); // Output: 1 - Indicates that a new empty data structure was created
      * ```
      */
-    public pfadd(key: string, elements: string[]): Promise {
+    public async pfadd(key: string, elements: string[]): Promise {
         return this.createWritePromise(createPfAdd(key, elements));
     }
 
     /** Estimates the cardinality of the data stored in a HyperLogLog structure for a single key or
      * calculates the combined cardinality of multiple keys by merging their HyperLogLogs temporarily.
      *
-     * See https://valkey.io/commands/pfcount/ for more details.
-     *
+     * @see {@link https://valkey.io/commands/pfcount/|valkey.io} for more details.
      * @remarks When in cluster mode, all `keys` must map to the same hash slot.
+     *
      * @param keys - The keys of the HyperLogLog data structures to be analyzed.
      * @returns - The approximated cardinality of given HyperLogLog data structures.
      *     The cardinality of a key that does not exist is `0`.
@@ -3944,7 +5129,7 @@ export class BaseClient {
      * console.log(result); // Output: 4 - The approximated cardinality of the union of "hll_1" and "hll_2"
      * ```
      */
-    public pfcount(keys: string[]): Promise {
+    public async pfcount(keys: string[]): Promise {
         return this.createWritePromise(createPfCount(keys));
     }
 
@@ -3952,9 +5137,9 @@ export class BaseClient {
      * Merges multiple HyperLogLog values into a unique value. If the destination variable exists, it is
      * treated as one of the source HyperLogLog data sets, otherwise a new HyperLogLog is created.
      *
-     * See https://valkey.io/commands/pfmerge/ for more details.
-     *
+     * @see {@link https://valkey.io/commands/pfmerge/|valkey.io} for more details.
      * @remarks When in Cluster mode, all keys in `sourceKeys` and `destination` must map to the same hash slot.
+     *
      * @param destination - The key of the destination HyperLogLog where the merged data sets will be stored.
      * @param sourceKeys - The keys of the HyperLogLog structures to be merged.
      * @returns A simple "OK" response.
@@ -3976,9 +5161,9 @@ export class BaseClient {
         return this.createWritePromise(createPfMerge(destination, sourceKeys));
     }
 
-    /** Returns the internal encoding for the Redis object stored at `key`.
+    /** Returns the internal encoding for the Valkey object stored at `key`.
      *
-     * See https://valkey.io/commands/object-encoding for more details.
+     * @see {@link https://valkey.io/commands/object-encoding/|valkey.io} for more details.
      *
      * @param key - The `key` of the object to get the internal encoding of.
      * @returns - If `key` exists, returns the internal encoding of the object stored at `key` as a string.
@@ -3989,13 +5174,13 @@ export class BaseClient {
      * console.log(result); // Output: "listpack"
      * ```
      */
-    public objectEncoding(key: string): Promise {
+    public async objectEncoding(key: string): Promise {
         return this.createWritePromise(createObjectEncoding(key));
     }
 
-    /** Returns the logarithmic access frequency counter of a Redis object stored at `key`.
+    /** Returns the logarithmic access frequency counter of a Valkey object stored at `key`.
      *
-     * See https://valkey.io/commands/object-freq for more details.
+     * @see {@link https://valkey.io/commands/object-freq/|valkey.io} for more details.
      *
      * @param key - The `key` of the object to get the logarithmic access frequency counter of.
      * @returns - If `key` exists, returns the logarithmic access frequency counter of the object
@@ -4006,14 +5191,14 @@ export class BaseClient {
      * console.log(result); // Output: 2 - The logarithmic access frequency counter of "my_hash".
      * ```
      */
-    public objectFreq(key: string): Promise {
+    public async objectFreq(key: string): Promise {
         return this.createWritePromise(createObjectFreq(key));
     }
 
     /**
      * Returns the time in seconds since the last access to the value stored at `key`.
      *
-     * See https://valkey.io/commands/object-idletime/ for more details.
+     * @see {@link https://valkey.io/commands/object-idletime/|valkey.io} for more details.
      *
      * @param key - The key of the object to get the idle time of.
      * @returns If `key` exists, returns the idle time in seconds. Otherwise, returns `null`.
@@ -4024,14 +5209,14 @@ export class BaseClient {
      * console.log(result); // Output: 13 - "my_hash" was last accessed 13 seconds ago.
      * ```
      */
-    public objectIdletime(key: string): Promise {
+    public async objectIdletime(key: string): Promise {
         return this.createWritePromise(createObjectIdletime(key));
     }
 
     /**
      * Returns the reference count of the object stored at `key`.
      *
-     * See https://valkey.io/commands/object-refcount/ for more details.
+     * @see {@link https://valkey.io/commands/object-refcount/|valkey.io} for more details.
      *
      * @param key - The `key` of the object to get the reference count of.
      * @returns If `key` exists, returns the reference count of the object stored at `key` as a `number`.
@@ -4043,18 +5228,17 @@ export class BaseClient {
      * console.log(result); // Output: 2 - "my_hash" has a reference count of 2.
      * ```
      */
-    public objectRefcount(key: string): Promise {
+    public async objectRefcount(key: string): Promise {
         return this.createWritePromise(createObjectRefcount(key));
     }
 
     /**
      * Invokes a previously loaded function.
      *
-     * See https://valkey.io/commands/fcall/ for more details.
-     *
-     * since Valkey version 7.0.0.
-     *
+     * @see {@link https://valkey.io/commands/fcall/|valkey.io} for more details.
      * @remarks When in cluster mode, all `keys` must map to the same hash slot.
+     * @remarks Since Valkey version 7.0.0.
+     *
      * @param func - The function name.
      * @param keys - A list of `keys` accessed by the function. To ensure the correct execution of functions,
      *     all names of keys that a function accesses must be explicitly provided as `keys`.
@@ -4067,7 +5251,7 @@ export class BaseClient {
      * console.log(response); // Output: Returns the function's return value.
      * ```
      */
-    public fcall(
+    public async fcall(
         func: string,
         keys: string[],
         args: string[],
@@ -4078,11 +5262,10 @@ export class BaseClient {
     /**
      * Invokes a previously loaded read-only function.
      *
-     * See https://valkey.io/commands/fcall/ for more details.
-     *
-     * since Valkey version 7.0.0.
-     *
+     * @see {@link https://valkey.io/commands/fcall/|valkey.io} for more details.
      * @remarks When in cluster mode, all `keys` must map to the same hash slot.
+     * @remarks Since Valkey version 7.0.0.
+     *
      * @param func - The function name.
      * @param keys - A list of `keys` accessed by the function. To ensure the correct execution of functions,
      *     all names of keys that a function accesses must be explicitly provided as `keys`.
@@ -4096,7 +5279,7 @@ export class BaseClient {
      * console.log(response); // Output: 42 # The return value on the function that was executed.
      * ```
      */
-    public fcallReadonly(
+    public async fcallReadonly(
         func: string,
         keys: string[],
         args: string[],
@@ -4109,7 +5292,8 @@ export class BaseClient {
      * match is found, `null` is returned. If the `count` option is specified, then the function returns
      * an `array` of indices of matching elements within the list.
      *
-     * See https://valkey.io/commands/lpos/ for more details.
+     * @see {@link https://valkey.io/commands/lpos/|valkey.io} for more details.
+     * @remarks Since Valkey version 6.0.6.
      *
      * @param key - The name of the list.
      * @param element - The value to search for within the list.
@@ -4117,8 +5301,6 @@ export class BaseClient {
      * @returns The index of `element`, or `null` if `element` is not in the list. If the `count` option
      * is specified, then the function returns an `array` of indices of matching elements within the list.
      *
-     * since - Valkey version 6.0.6.
-     *
      * @example
      * ```typescript
      * await client.rpush("myList", ["a", "b", "c", "d", "e", "e"]);
@@ -4126,7 +5308,7 @@ export class BaseClient {
      * console.log(await client.lpos("myList", "e", { count: 3 })); // Output: [ 4, 5 ] - indices for the occurrences of "e" in list "myList".
      * ```
      */
-    public lpos(
+    public async lpos(
         key: string,
         element: string,
         options?: LPosOptions,
@@ -4138,7 +5320,7 @@ export class BaseClient {
      * Counts the number of set bits (population counting) in the string stored at `key`. The `options` argument can
      * optionally be provided to count the number of bits in a specific string interval.
      *
-     * See https://valkey.io/commands/bitcount for more details.
+     * @see {@link https://valkey.io/commands/bitcount/|valkey.io} for more details.
      *
      * @param key - The key for the string to count the set bits of.
      * @param options - The offset options.
@@ -4154,7 +5336,10 @@ export class BaseClient {
      * console.log(await client.bitcount("my_key3", { start: -1, end: -1, indexType: BitmapIndexType.BIT })); // Output: 1 - Indicates that the last bit of the string stored at "my_key3" is set.
      * ```
      */
-    public bitcount(key: string, options?: BitOffsetOptions): Promise {
+    public async bitcount(
+        key: string,
+        options?: BitOffsetOptions,
+    ): Promise {
         return this.createWritePromise(createBitCount(key, options));
     }
 
@@ -4162,7 +5347,7 @@ export class BaseClient {
      * Adds geospatial members with their positions to the specified sorted set stored at `key`.
      * If a member is already a part of the sorted set, its position is updated.
      *
-     * See https://valkey.io/commands/geoadd/ for more details.
+     * @see {@link https://valkey.io/commands/geoadd/|valkey.io} for more details.
      *
      * @param key - The key of the sorted set.
      * @param membersToGeospatialData - A mapping of member names to their corresponding positions - see
@@ -4182,7 +5367,7 @@ export class BaseClient {
      * console.log(num); // Output: 1 - Indicates that the position of an existing member in the sorted set "mySortedSet" has been updated.
      * ```
      */
-    public geoadd(
+    public async geoadd(
         key: string,
         membersToGeospatialData: Map,
         options?: GeoAddOptions,
@@ -4196,28 +5381,20 @@ export class BaseClient {
      * Returns the members of a sorted set populated with geospatial information using {@link geoadd},
      * which are within the borders of the area specified by a given shape.
      *
-     * See https://valkey.io/commands/geosearch/ for more details.
-     *
-     * since - Valkey 6.2.0 and above.
+     * @see {@link https://valkey.io/commands/geosearch/|valkey.io} for more details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param key - The key of the sorted set.
      * @param searchFrom - The query's center point options, could be one of:
-     *
      * - {@link MemberOrigin} to use the position of the given existing member in the sorted set.
-     *
      * - {@link CoordOrigin} to use the given longitude and latitude coordinates.
-     *
      * @param searchBy - The query's shape options, could be one of:
-     *
      * - {@link GeoCircleShape} to search inside circular area according to given radius.
-     *
      * - {@link GeoBoxShape} to search inside an axis-aligned rectangle, determined by height and width.
-     *
-     * @param resultOptions - The optional inputs to request additional information and configure sorting/limiting the results, see {@link GeoSearchResultOptions}.
+     * @param resultOptions - (Optional) Parameters to request additional information and configure sorting/limiting the results, see {@link GeoSearchResultOptions}.
      * @returns By default, returns an `Array` of members (locations) names.
      *     If any of `withCoord`, `withDist` or `withHash` are set to `true` in {@link GeoSearchResultOptions}, a 2D `Array` returned,
      *     where each sub-array represents a single item in the following order:
-     *
      * - The member (location) name.
      * - The distance from the center as a floating point `number`, in the same unit specified for `searchBy`, if `withDist` is set to `true`.
      * - The geohash of the location as a integer `number`, if `withHash` is set to `true`.
@@ -4271,17 +5448,94 @@ export class BaseClient {
         searchFrom: SearchOrigin,
         searchBy: GeoSearchShape,
         resultOptions?: GeoSearchResultOptions,
-    ): Promise<(Buffer | (number | number[])[])[]> {
+    ): Promise<(string | (number | number[])[])[]> {
         return this.createWritePromise(
             createGeoSearch(key, searchFrom, searchBy, resultOptions),
         );
     }
 
+    /**
+     * Searches for members in a sorted set stored at `source` representing geospatial data
+     * within a circular or rectangular area and stores the result in `destination`.
+     *
+     * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created.
+     *
+     * To get the result directly, see {@link geosearch}.
+     *
+     * @see {@link https://valkey.io/commands/geosearchstore/|valkey.io} for more details.
+     * @remarks When in cluster mode, `destination` and `source` must map to the same hash slot.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param destination - The key of the destination sorted set.
+     * @param source - The key of the sorted set.
+     * @param searchFrom - The query's center point options, could be one of:
+     * - {@link MemberOrigin} to use the position of the given existing member in the sorted set.
+     * - {@link CoordOrigin} to use the given longitude and latitude coordinates.
+     * @param searchBy - The query's shape options, could be one of:
+     * - {@link GeoCircleShape} to search inside circular area according to given radius.
+     * - {@link GeoBoxShape} to search inside an axis-aligned rectangle, determined by height and width.
+     * @param resultOptions - (Optional) Parameters to request additional information and configure sorting/limiting the results, see {@link GeoSearchStoreResultOptions}.
+     * @returns The number of elements in the resulting sorted set stored at `destination`.
+     *
+     * @example
+     * ```typescript
+     * const data = new Map([["Palermo", { longitude: 13.361389, latitude: 38.115556 }], ["Catania", { longitude: 15.087269, latitude: 37.502669 }]]);
+     * await client.geoadd("mySortedSet", data);
+     * // search for locations within 200 km circle around stored member named 'Palermo' and store in `destination`:
+     * await client.geosearchstore("destination", "mySortedSet", { member: "Palermo" }, { radius: 200, unit: GeoUnit.KILOMETERS });
+     * // query the stored results
+     * const result1 = await client.zrangeWithScores("destination", { start: 0, stop: -1 });
+     * console.log(result1); // Output:
+     * // {
+     * //     Palermo: 3479099956230698,   // geohash of the location is stored as element's score
+     * //     Catania: 3479447370796909
+     * // }
+     *
+     * // search for locations in 200x300 mi rectangle centered at coordinate (15, 37), requesting to store distance instead of geohashes,
+     * // limiting results by 2 best matches, ordered by ascending distance from the search area center
+     * await client.geosearchstore(
+     *     "destination",
+     *     "mySortedSet",
+     *     { position: { longitude: 15, latitude: 37 } },
+     *     { width: 200, height: 300, unit: GeoUnit.MILES },
+     *     {
+     *         sortOrder: SortOrder.ASC,
+     *         count: 2,
+     *         storeDist: true,
+     *     },
+     * );
+     * // query the stored results
+     * const result2 = await client.zrangeWithScores("destination", { start: 0, stop: -1 });
+     * console.log(result2); // Output:
+     * // {
+     * //     Palermo: 190.4424,   // distance from the search area center is stored as element's score
+     * //     Catania: 56.4413,    // the distance is measured in units used for the search query (miles)
+     * // }
+     * ```
+     */
+    public async geosearchstore(
+        destination: string,
+        source: string,
+        searchFrom: SearchOrigin,
+        searchBy: GeoSearchShape,
+        resultOptions?: GeoSearchStoreResultOptions,
+    ): Promise {
+        return this.createWritePromise(
+            createGeoSearchStore(
+                destination,
+                source,
+                searchFrom,
+                searchBy,
+                resultOptions,
+            ),
+        );
+    }
+
     /**
      * Returns the positions (longitude, latitude) of all the specified `members` of the
      * geospatial index represented by the sorted set at `key`.
      *
-     * See https://valkey.io/commands/geopos for more details.
+     * @see {@link https://valkey.io/commands/geopos/|valkey.io} for more details.
      *
      * @param key - The key of the sorted set.
      * @param members - The members for which to get the positions.
@@ -4299,7 +5553,7 @@ export class BaseClient {
      * console.log(result); // Output: [[13.36138933897018433, 38.11555639549629859], [15.08726745843887329, 37.50266842333162032], null]
      * ```
      */
-    public geopos(
+    public async geopos(
         key: string,
         members: string[],
     ): Promise<(number[] | null)[]> {
@@ -4310,9 +5564,10 @@ export class BaseClient {
      * Pops a member-score pair from the first non-empty sorted set, with the given `keys`
      * being checked in the order they are provided.
      *
-     * See https://valkey.io/commands/zmpop/ for more details.
-     *
+     * @see {@link https://valkey.io/commands/zmpop/|valkey.io} for more details.
      * @remarks When in cluster mode, all `keys` must map to the same hash slot.
+     * @remarks Since Valkey version 7.0.0.
+     *
      * @param keys - The keys of the sorted sets.
      * @param modifier - The element pop criteria - either {@link ScoreFilter.MIN} or
      *     {@link ScoreFilter.MAX} to pop the member with the lowest/highest score accordingly.
@@ -4321,8 +5576,6 @@ export class BaseClient {
      *     was popped, and a member-score `Record` of the popped element.
      *     If no member could be popped, returns `null`.
      *
-     * since Valkey version 7.0.0.
-     *
      * @example
      * ```typescript
      * await client.zadd("zSet1", { one: 1.0, two: 2.0, three: 3.0 });
@@ -4344,12 +5597,11 @@ export class BaseClient {
      * checked in the order they are provided. Blocks the connection when there are no members
      * to pop from any of the given sorted sets. `BZMPOP` is the blocking variant of {@link zmpop}.
      *
-     * See https://valkey.io/commands/bzmpop/ for more details.
+     * @see {@link https://valkey.io/commands/bzmpop/|valkey.io} for more details.
+     * @remarks When in cluster mode, all `keys` must map to the same hash slot.
+     * @remarks `BZMPOP` is a client blocking command, see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands | Valkey Glide Wiki} for more details and best practices.
+     * @remarks Since Valkey version 7.0.0.
      *
-     * @remarks
-     *      1. When in cluster mode, all `keys` must map to the same hash slot.
-     *      2. `BZMPOP` is a client blocking command, see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands | the wiki}
-     *         for more details and best practices.
      * @param keys - The keys of the sorted sets.
      * @param modifier - The element pop criteria - either {@link ScoreFilter.MIN} or
      *     {@link ScoreFilter.MAX} to pop the member with the lowest/highest score accordingly.
@@ -4360,8 +5612,6 @@ export class BaseClient {
      *     was popped, and a member-score `Record` of the popped element.
      *     If no member could be popped, returns `null`.
      *
-     * since Valkey version 7.0.0.
-     *
      * @example
      * ```typescript
      * await client.zadd("zSet1", { one: 1.0, two: 2.0, three: 3.0 });
@@ -4386,7 +5636,7 @@ export class BaseClient {
      * If `member` does not exist in the sorted set, it is added with `increment` as its score.
      * If `key` does not exist, a new sorted set is created with the specified member as its sole member.
      *
-     * See https://valkey.io/commands/zincrby/ for details.
+     * @see {@link https://valkey.io/commands/zincrby/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param increment - The score increment.
@@ -4417,7 +5667,7 @@ export class BaseClient {
     /**
      * Iterates incrementally over a sorted set.
      *
-     * See https://valkey.io/commands/zscan for more details.
+     * @see {@link https://valkey.io/commands/zscan/|valkey.io} for more details.
      *
      * @param key - The key of the sorted set.
      * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of
@@ -4464,7 +5714,7 @@ export class BaseClient {
     /**
      * Returns the distance between `member1` and `member2` saved in the geospatial index stored at `key`.
      *
-     * See https://valkey.io/commands/geodist/ for more details.
+     * @see {@link https://valkey.io/commands/geodist/|valkey.io} for more details.
      *
      * @param key - The key of the sorted set.
      * @param member1 - The name of the first member.
@@ -4479,7 +5729,7 @@ export class BaseClient {
      * console.log(num); // Output: the distance between Place1 and Place2.
      * ```
      */
-    public geodist(
+    public async geodist(
         key: string,
         member1: string,
         member2: string,
@@ -4493,7 +5743,7 @@ export class BaseClient {
     /**
      * Returns the `GeoHash` strings representing the positions of all the specified `members` in the sorted set stored at `key`.
      *
-     * See https://valkey.io/commands/geohash/ for more details.
+     * @see {@link https://valkey.io/commands/geohash/|valkey.io} for more details.
      *
      * @param key - The key of the sorted set.
      * @param members - The array of members whose `GeoHash` strings are to be retrieved.
@@ -4506,7 +5756,10 @@ export class BaseClient {
      * console.log(num); // Output: ["sqc8b49rny0", "sqdtr74hyu0", null]
      * ```
      */
-    public geohash(key: string, members: string[]): Promise<(string | null)[]> {
+    public async geohash(
+        key: string,
+        members: string[],
+    ): Promise<(string | null)[]> {
         return this.createWritePromise<(string | null)[]>(
             createGeoHash(key, members),
         ).then((hashes) =>
@@ -4517,11 +5770,9 @@ export class BaseClient {
     /**
      * Returns all the longest common subsequences combined between strings stored at `key1` and `key2`.
      *
-     * since Valkey version 7.0.0.
-     *
+     * @see {@link https://valkey.io/commands/lcs/|valkey.io} for more details.
      * @remarks When in cluster mode, `key1` and `key2` must map to the same hash slot.
-     *
-     * See https://valkey.io/commands/lcs/ for more details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key1 - The key that stores the first string.
      * @param key2 - The key that stores the second string.
@@ -4532,7 +5783,7 @@ export class BaseClient {
      * ```typescript
      * await client.mset({"testKey1": "abcd", "testKey2": "axcd"});
      * const result = await client.lcs("testKey1", "testKey2");
-     * console.log(result); // Output: 'cd'
+     * console.log(result); // Output: 'acd'
      * ```
      */
     public async lcs(key1: string, key2: string): Promise {
@@ -4542,11 +5793,9 @@ export class BaseClient {
     /**
      * Returns the total length of all the longest common subsequences between strings stored at `key1` and `key2`.
      *
-     * since Valkey version 7.0.0.
-     *
+     * @see {@link https://valkey.io/commands/lcs/|valkey.io} for more details.
      * @remarks When in cluster mode, `key1` and `key2` must map to the same hash slot.
-     *
-     * See https://valkey.io/commands/lcs/ for more details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key1 - The key that stores the first string.
      * @param key2 - The key that stores the second string.
@@ -4556,7 +5805,7 @@ export class BaseClient {
      * ```typescript
      * await client.mset({"testKey1": "abcd", "testKey2": "axcd"});
      * const result = await client.lcsLen("testKey1", "testKey2");
-     * console.log(result); // Output: 2
+     * console.log(result); // Output: 3
      * ```
      */
     public async lcsLen(key1: string, key2: string): Promise {
@@ -4567,11 +5816,9 @@ export class BaseClient {
      * Returns the indices and lengths of the longest common subsequences between strings stored at
      * `key1` and `key2`.
      *
-     * since Valkey version 7.0.0.
-     *
+     * @see {@link https://valkey.io/commands/lcs/|valkey.io} for more details.
      * @remarks When in cluster mode, `key1` and `key2` must map to the same hash slot.
-     *
-     * See https://valkey.io/commands/lcs/ for more details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key1 - The key that stores the first string.
      * @param key2 - The key that stores the second string.
@@ -4586,6 +5833,8 @@ export class BaseClient {
      *           of indices that represent the location of the common subsequences in the strings held
      *           by `key1` and `key2`.
      *
+     *     See example for more details.
+     *
      * @example
      * ```typescript
      * await client.mset({"key1": "ohmytext", "key2": "mynewtext"});
@@ -4622,9 +5871,9 @@ export class BaseClient {
     /**
      * Updates the last access time of the specified keys.
      *
-     * See https://valkey.io/commands/touch/ for more details.
-     *
+     * @see {@link https://valkey.io/commands/touch/|valkey.io} for more details.
      * @remarks When in cluster mode, the command may route to multiple nodes when `keys` map to different hash slots.
+     *
      * @param keys - The keys to update the last access time of.
      * @returns The number of keys that were updated. A key is ignored if it doesn't exist.
      *
@@ -4645,9 +5894,9 @@ export class BaseClient {
      * will only execute commands if the watched keys are not modified before execution of the
      * transaction. Executing a transaction will automatically flush all previously watched keys.
      *
-     * See https://valkey.io/commands/watch/ and https://valkey.io/topics/transactions/#cas for more details.
-     *
+     * @see {@link https://valkey.io/commands/watch/|valkey.io} and {@link https://valkey.io/topics/transactions/#cas|Valkey Glide Wiki} for more details.
      * @remarks When in cluster mode, the command may route to multiple nodes when `keys` map to different hash slots.
+     *
      * @param keys - The keys to watch.
      * @returns A simple "OK" response.
      *
@@ -4672,12 +5921,34 @@ export class BaseClient {
         return this.createWritePromise(createWatch(keys));
     }
 
+    /**
+     * Blocks the current client until all the previous write commands are successfully transferred and
+     * acknowledged by at least `numreplicas` of replicas. If `timeout` is reached, the command returns
+     * the number of replicas that were not yet reached.
+     *
+     * @see {@link https://valkey.io/commands/wait/|valkey.io} for more details.
+     *
+     * @param numreplicas - The number of replicas to reach.
+     * @param timeout - The timeout value specified in milliseconds. A value of 0 will block indefinitely.
+     * @returns The number of replicas reached by all the writes performed in the context of the current connection.
+     *
+     * @example
+     * ```typescript
+     * await client.set(key, value);
+     * let response = await client.wait(1, 1000);
+     * console.log(response); // Output: return 1 when a replica is reached or 0 if 1000ms is reached.
+     * ```
+     */
+    public async wait(numreplicas: number, timeout: number): Promise {
+        return this.createWritePromise(createWait(numreplicas, timeout));
+    }
+
     /**
      * Overwrites part of the string stored at `key`, starting at the specified `offset`,
      * for the entire length of `value`. If the `offset` is larger than the current length of the string at `key`,
      * the string is padded with zero bytes to make `offset` fit. Creates the `key` if it doesn't exist.
      *
-     * See https://valkey.io/commands/setrange/ for more details.
+     * @see {@link https://valkey.io/commands/setrange/|valkey.io} for more details.
      *
      * @param key - The key of the string to update.
      * @param offset - The position in the string where `value` should be written.
@@ -4701,18 +5972,43 @@ export class BaseClient {
     }
 
     /**
-     * Pops one or more elements from the first non-empty list from the provided `keys`.
+     * Appends a `value` to a `key`. If `key` does not exist it is created and set as an empty string,
+     * so `APPEND` will be similar to {@link set} in this special case.
+     *
+     * @see {@link https://valkey.io/commands/append/|valkey.io} for more details.
+     *
+     * @param key - The key of the string.
+     * @param value - The key of the string.
+     * @returns The length of the string after appending the value.
      *
-     * See https://valkey.io/commands/lmpop/ for more details.
+     * @example
+     * ```typescript
+     * const len = await client.append("key", "Hello");
+     * console.log(len);
+     *     // Output: 5 - Indicates that "Hello" has been appended to the value of "key", which was initially
+     *     // empty, resulting in a new value of "Hello" with a length of 5 - similar to the set operation.
+     * len = await client.append("key", " world");
+     * console.log(result);
+     *     // Output: 11 - Indicates that " world" has been appended to the value of "key", resulting in a
+     *     // new value of "Hello world" with a length of 11.
+     * ```
+     */
+    public async append(key: GlideString, value: GlideString): Promise {
+        return this.createWritePromise(createAppend(key, value));
+    }
+
+    /**
+     * Pops one or more elements from the first non-empty list from the provided `keys`.
      *
+     * @see {@link https://valkey.io/commands/lmpop/|valkey.io} for more details.
      * @remarks When in cluster mode, all `key`s must map to the same hash slot.
+     * @remarks Since Valkey version 7.0.0.
+     *
      * @param keys - An array of keys to lists.
      * @param direction - The direction based on which elements are popped from - see {@link ListDirection}.
      * @param count - (Optional) The maximum number of popped elements.
      * @returns A `Record` of key-name mapped array of popped elements.
      *
-     * since Valkey version 7.0.0.
-     *
      * @example
      * ```typescript
      * await client.lpush("testKey", ["one", "two", "three"]);
@@ -4733,9 +6029,10 @@ export class BaseClient {
      * Blocks the connection until it pops one or more elements from the first non-empty list from the
      * provided `key`. `BLMPOP` is the blocking variant of {@link lmpop}.
      *
-     * See https://valkey.io/commands/blmpop/ for more details.
-     *
+     * @see {@link https://valkey.io/commands/blmpop/|valkey.io} for more details.
      * @remarks When in cluster mode, all `key`s must map to the same hash slot.
+     * @remarks Since Valkey version 7.0.0.
+     *
      * @param keys - An array of keys to lists.
      * @param direction - The direction based on which elements are popped from - see {@link ListDirection}.
      * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of `0` will block indefinitely.
@@ -4743,8 +6040,6 @@ export class BaseClient {
      * @returns - A `Record` of `key` name mapped array of popped elements.
      *     If no member could be popped and the timeout expired, returns `null`.
      *
-     * since Valkey version 7.0.0.
-     *
      * @example
      * ```typescript
      * await client.lpush("testKey", ["one", "two", "three"]);
@@ -4768,7 +6063,7 @@ export class BaseClient {
      * Lists the currently active channels.
      * The command is routed to all nodes, and aggregates the response to a single array.
      *
-     * See https://valkey.io/commands/pubsub-channels for more details.
+     * @see {@link https://valkey.io/commands/pubsub-channels/|valkey.io} for more details.
      *
      * @param pattern - A glob-style pattern to match active channels.
      *                  If not provided, all active channels are returned.
@@ -4795,7 +6090,7 @@ export class BaseClient {
      * not the count of clients subscribed to patterns.
      * The command is routed to all nodes, and aggregates the response to the sum of all pattern subscriptions.
      *
-     * See https://valkey.io/commands/pubsub-numpat for more details.
+     * @see {@link https://valkey.io/commands/pubsub-numpat/|valkey.io} for more details.
      *
      * @returns The number of unique patterns.
      *
@@ -4815,7 +6110,7 @@ export class BaseClient {
      * Note that it is valid to call this command without channels. In this case, it will just return an empty map.
      * The command is routed to all nodes, and aggregates the response to a single map of the channels and their number of subscriptions.
      *
-     * See https://valkey.io/commands/pubsub-numsub for more details.
+     * @see {@link https://valkey.io/commands/pubsub-numsub/|valkey.io} for more details.
      *
      * @param channels - The list of channels to query for the number of subscribers.
      *                   If not provided, returns an empty map.
diff --git a/node/src/Commands.ts b/node/src/Commands.ts
index fd0402b299..99eddc6767 100644
--- a/node/src/Commands.ts
+++ b/node/src/Commands.ts
@@ -6,16 +6,17 @@ import { createLeakedStringVec, MAX_REQUEST_ARGS_LEN } from "glide-rs";
 import Long from "long";
 
 /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
-import { BaseClient } from "src/BaseClient";
+import { BaseClient, Decoder } from "src/BaseClient";
 /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
 import { GlideClient } from "src/GlideClient";
 /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
 import { GlideClusterClient } from "src/GlideClusterClient";
+import { GlideString } from "./BaseClient";
 import { command_request } from "./ProtobufMessage";
 
 import RequestType = command_request.RequestType;
 
-function isLargeCommand(args: BulkString[]) {
+function isLargeCommand(args: GlideString[]) {
     let lenSum = 0;
 
     for (const arg of args) {
@@ -29,12 +30,10 @@ function isLargeCommand(args: BulkString[]) {
     return false;
 }
 
-type BulkString = string | Uint8Array;
-
 /**
  * Convert a string array into Uint8Array[]
  */
-function toBuffersArray(args: BulkString[]) {
+function toBuffersArray(args: GlideString[]) {
     const argsBytes: Uint8Array[] = [];
 
     for (const arg of args) {
@@ -68,7 +67,7 @@ export function parseInfoResponse(response: string): Record {
 
 function createCommand(
     requestType: command_request.RequestType,
-    args: BulkString[],
+    args: GlideString[],
 ): command_request.Command {
     const singleCommand = command_request.Command.create({
         requestType,
@@ -93,14 +92,14 @@ function createCommand(
 /**
  * @internal
  */
-export function createGet(key: string): command_request.Command {
+export function createGet(key: GlideString): command_request.Command {
     return createCommand(RequestType.Get, [key]);
 }
 
 /**
  * @internal
  */
-export function createGetDel(key: string): command_request.Command {
+export function createGetDel(key: GlideString): command_request.Command {
     return createCommand(RequestType.GetDel, [key]);
 }
 
@@ -108,7 +107,7 @@ export function createGetDel(key: string): command_request.Command {
  * @internal
  */
 export function createGetRange(
-    key: string,
+    key: GlideString,
     start: number,
     end: number,
 ): command_request.Command {
@@ -143,26 +142,7 @@ export type SetOptions = {
      */
     | "keepExisting"
         | {
-              type: /**
-               * Set the specified expire time, in seconds. Equivalent to
-               * `EX` in the Redis API.
-               */
-              | "seconds"
-                  /**
-                   * Set the specified expire time, in milliseconds. Equivalent
-                   * to `PX` in the Redis API.
-                   */
-                  | "milliseconds"
-                  /**
-                   * Set the specified Unix time at which the key will expire,
-                   * in seconds. Equivalent to `EXAT` in the Redis API.
-                   */
-                  | "unixSeconds"
-                  /**
-                   * Set the specified Unix time at which the key will expire,
-                   * in milliseconds. Equivalent to `PXAT` in the Redis API.
-                   */
-                  | "unixMilliseconds";
+              type: TimeUnit;
               count: number;
           };
 };
@@ -171,8 +151,8 @@ export type SetOptions = {
  * @internal
  */
 export function createSet(
-    key: BulkString,
-    value: BulkString,
+    key: GlideString,
+    value: GlideString,
     options?: SetOptions,
 ): command_request.Command {
     const args = [key, value];
@@ -188,28 +168,23 @@ export function createSet(
             args.push("GET");
         }
 
-        if (
-            options.expiry &&
-            options.expiry !== "keepExisting" &&
-            !Number.isInteger(options.expiry.count)
-        ) {
-            throw new Error(
-                `Received expiry '${JSON.stringify(
-                    options.expiry,
-                )}'. Count must be an integer`,
-            );
-        }
+        if (options.expiry) {
+            if (
+                options.expiry !== "keepExisting" &&
+                !Number.isInteger(options.expiry.count)
+            ) {
+                throw new Error(
+                    `Received expiry '${JSON.stringify(
+                        options.expiry,
+                    )}'. Count must be an integer`,
+                );
+            }
 
-        if (options.expiry === "keepExisting") {
-            args.push("KEEPTTL");
-        } else if (options.expiry?.type === "seconds") {
-            args.push("EX", options.expiry.count.toString());
-        } else if (options.expiry?.type === "milliseconds") {
-            args.push("PX", options.expiry.count.toString());
-        } else if (options.expiry?.type === "unixSeconds") {
-            args.push("EXAT", options.expiry.count.toString());
-        } else if (options.expiry?.type === "unixMilliseconds") {
-            args.push("PXAT", options.expiry.count.toString());
+            if (options.expiry === "keepExisting") {
+                args.push("KEEPTTL");
+            } else {
+                args.push(options.expiry.type, options.expiry.count.toString());
+            }
         }
     }
 
@@ -294,8 +269,8 @@ export enum InfoOptions {
 /**
  * @internal
  */
-export function createPing(str?: string): command_request.Command {
-    const args: string[] = str == undefined ? [] : [str];
+export function createPing(str?: GlideString): command_request.Command {
+    const args: GlideString[] = str == undefined ? [] : [str];
     return createCommand(RequestType.Ping, args);
 }
 
@@ -345,7 +320,7 @@ export function createConfigResetStat(): command_request.Command {
 /**
  * @internal
  */
-export function createMGet(keys: string[]): command_request.Command {
+export function createMGet(keys: GlideString[]): command_request.Command {
     return createCommand(RequestType.MGet, keys);
 }
 
@@ -427,8 +402,8 @@ export function createConfigSet(
  * @internal
  */
 export function createHGet(
-    key: string,
-    field: string,
+    key: GlideString,
+    field: GlideString,
 ): command_request.Command {
     return createCommand(RequestType.HGet, [key, field]);
 }
@@ -446,6 +421,13 @@ export function createHSet(
     );
 }
 
+/**
+ * @internal
+ */
+export function createHKeys(key: string): command_request.Command {
+    return createCommand(RequestType.HKeys, [key]);
+}
+
 /**
  * @internal
  */
@@ -1043,6 +1025,23 @@ export function createSRem(
     return createCommand(RequestType.SRem, [key].concat(members));
 }
 
+/**
+ * @internal
+ */
+export function createSScan(
+    key: string,
+    cursor: string,
+    options?: BaseScanOptions,
+): command_request.Command {
+    let args: string[] = [key, cursor];
+
+    if (options) {
+        args = args.concat(convertBaseScanOptionsToArgsArray(options));
+    }
+
+    return createCommand(RequestType.SScan, args);
+}
+
 /**
  * @internal
  */
@@ -1171,7 +1170,18 @@ export function createSPop(
 /**
  * @internal
  */
-export function createCustomCommand(args: string[]) {
+export function createSRandMember(
+    key: string,
+    count?: number,
+): command_request.Command {
+    const args: string[] = count == undefined ? [key] : [key, count.toString()];
+    return createCommand(RequestType.SRandMember, args);
+}
+
+/**
+ * @internal
+ */
+export function createCustomCommand(args: GlideString[]) {
     return createCommand(RequestType.CustomCommand, args);
 }
 
@@ -1211,7 +1221,7 @@ export function createHLen(key: string): command_request.Command {
 /**
  * @internal
  */
-export function createHVals(key: string): command_request.Command {
+export function createHVals(key: GlideString): command_request.Command {
     return createCommand(RequestType.HVals, [key]);
 }
 
@@ -1532,35 +1542,35 @@ export function createZMScore(
     return createCommand(RequestType.ZMScore, [key, ...members]);
 }
 
-export enum InfScoreBoundary {
+export enum InfBoundary {
     /**
-     * Positive infinity bound for sorted set.
+     * Positive infinity bound.
      */
     PositiveInfinity = "+",
     /**
-     * Negative infinity bound for sorted set.
+     * Negative infinity bound.
      */
     NegativeInfinity = "-",
 }
 
 /**
- * Defines where to insert new elements into a list.
+ * Defines the boundaries of a range.
  */
-export type ScoreBoundary =
+export type Boundary =
     /**
-     *  Represents an lower/upper boundary in a sorted set.
+     *  Represents an lower/upper boundary.
      */
-    | InfScoreBoundary
+    | InfBoundary
     /**
-     *  Represents a specific numeric score boundary in a sorted set.
+     *  Represents a specific boundary.
      */
     | {
           /**
-           * The score value.
+           * The comparison value.
            */
           value: T;
           /**
-           * Whether the score value is inclusive. Defaults to True.
+           * Whether the value is inclusive. Defaults to `true`.
            */
           isInclusive?: boolean;
       };
@@ -1588,11 +1598,11 @@ type SortedSetRange = {
     /**
      * The start boundary.
      */
-    start: ScoreBoundary;
+    start: Boundary;
     /**
      * The stop boundary.
      */
-    stop: ScoreBoundary;
+    stop: Boundary;
     /**
      * The limit argument for a range query.
      * Represents a limit argument for a range query in a sorted set to
@@ -1617,37 +1627,50 @@ type SortedSetRange = {
 export type RangeByScore = SortedSetRange & { type: "byScore" };
 export type RangeByLex = SortedSetRange & { type: "byLex" };
 
-/**
- * Returns a string representation of a score boundary in Redis protocol format.
- * @param score - The score boundary object containing value and inclusivity
- *     information.
- * @param isLex - Indicates whether to return lexical representation for
- *     positive/negative infinity.
- * @returns A string representation of the score boundary in Redis protocol
- *     format.
- */
+/** Returns a string representation of a score boundary as a command argument. */
 function getScoreBoundaryArg(
-    score: ScoreBoundary | ScoreBoundary,
-    isLex: boolean = false,
+    score: Boundary | Boundary,
 ): string {
-    if (score == InfScoreBoundary.PositiveInfinity) {
-        return (
-            InfScoreBoundary.PositiveInfinity.toString() + (isLex ? "" : "inf")
-        );
+    if (typeof score === "string") {
+        // InfBoundary
+        return score + "inf";
     }
 
-    if (score == InfScoreBoundary.NegativeInfinity) {
-        return (
-            InfScoreBoundary.NegativeInfinity.toString() + (isLex ? "" : "inf")
-        );
+    if (score.isInclusive == false) {
+        return "(" + score.value.toString();
+    }
+
+    return score.value.toString();
+}
+
+/** Returns a string representation of a lex boundary as a command argument. */
+function getLexBoundaryArg(score: Boundary | Boundary): string {
+    if (typeof score === "string") {
+        // InfBoundary
+        return score;
     }
 
     if (score.isInclusive == false) {
         return "(" + score.value.toString();
     }
 
-    const value = isLex ? "[" + score.value.toString() : score.value.toString();
-    return value;
+    return "[" + score.value.toString();
+}
+
+/** Returns a string representation of a stream boundary as a command argument. */
+function getStreamBoundaryArg(
+    boundary: Boundary | Boundary,
+): string {
+    if (typeof boundary === "string") {
+        // InfBoundary
+        return boundary;
+    }
+
+    if (boundary.isInclusive == false) {
+        return "(" + boundary.value.toString();
+    }
+
+    return boundary.value.toString();
 }
 
 function createZRangeArgs(
@@ -1660,10 +1683,20 @@ function createZRangeArgs(
 
     if (typeof rangeQuery.start != "number") {
         rangeQuery = rangeQuery as RangeByScore | RangeByLex;
-        const isLex = rangeQuery.type == "byLex";
-        args.push(getScoreBoundaryArg(rangeQuery.start, isLex));
-        args.push(getScoreBoundaryArg(rangeQuery.stop, isLex));
-        args.push(isLex == true ? "BYLEX" : "BYSCORE");
+
+        if (rangeQuery.type == "byLex") {
+            args.push(
+                getLexBoundaryArg(rangeQuery.start),
+                getLexBoundaryArg(rangeQuery.stop),
+                "BYLEX",
+            );
+        } else {
+            args.push(
+                getScoreBoundaryArg(rangeQuery.start),
+                getScoreBoundaryArg(rangeQuery.stop),
+                "BYSCORE",
+            );
+        }
     } else {
         args.push(rangeQuery.start.toString());
         args.push(rangeQuery.stop.toString());
@@ -1693,12 +1726,14 @@ function createZRangeArgs(
  */
 export function createZCount(
     key: string,
-    minScore: ScoreBoundary,
-    maxScore: ScoreBoundary,
+    minScore: Boundary,
+    maxScore: Boundary,
 ): command_request.Command {
-    const args = [key];
-    args.push(getScoreBoundaryArg(minScore));
-    args.push(getScoreBoundaryArg(maxScore));
+    const args = [
+        key,
+        getScoreBoundaryArg(minScore),
+        getScoreBoundaryArg(maxScore),
+    ];
     return createCommand(RequestType.ZCount, args);
 }
 
@@ -1726,6 +1761,22 @@ export function createZRangeWithScores(
     return createCommand(RequestType.ZRange, args);
 }
 
+/**
+ * @internal
+ */
+export function createZRangeStore(
+    destination: string,
+    source: string,
+    rangeQuery: RangeByIndex | RangeByScore | RangeByLex,
+    reverse: boolean = false,
+): command_request.Command {
+    const args = [
+        destination,
+        ...createZRangeArgs(source, rangeQuery, reverse, false),
+    ];
+    return createCommand(RequestType.ZRangeStore, args);
+}
+
 /**
  * @internal
  */
@@ -1832,14 +1883,10 @@ export function createZRemRangeByRank(
  */
 export function createZRemRangeByLex(
     key: string,
-    minLex: ScoreBoundary,
-    maxLex: ScoreBoundary,
+    minLex: Boundary,
+    maxLex: Boundary,
 ): command_request.Command {
-    const args = [
-        key,
-        getScoreBoundaryArg(minLex, true),
-        getScoreBoundaryArg(maxLex, true),
-    ];
+    const args = [key, getLexBoundaryArg(minLex), getLexBoundaryArg(maxLex)];
     return createCommand(RequestType.ZRemRangeByLex, args);
 }
 
@@ -1848,15 +1895,18 @@ export function createZRemRangeByLex(
  */
 export function createZRemRangeByScore(
     key: string,
-    minScore: ScoreBoundary,
-    maxScore: ScoreBoundary,
+    minScore: Boundary,
+    maxScore: Boundary,
 ): command_request.Command {
-    const args = [key];
-    args.push(getScoreBoundaryArg(minScore));
-    args.push(getScoreBoundaryArg(maxScore));
+    const args = [
+        key,
+        getScoreBoundaryArg(minScore),
+        getScoreBoundaryArg(maxScore),
+    ];
     return createCommand(RequestType.ZRemRangeByScore, args);
 }
 
+/** @internal */
 export function createPersist(key: string): command_request.Command {
     return createCommand(RequestType.Persist, [key]);
 }
@@ -1866,14 +1916,10 @@ export function createPersist(key: string): command_request.Command {
  */
 export function createZLexCount(
     key: string,
-    minLex: ScoreBoundary,
-    maxLex: ScoreBoundary,
+    minLex: Boundary,
+    maxLex: Boundary,
 ): command_request.Command {
-    const args = [
-        key,
-        getScoreBoundaryArg(minLex, true),
-        getScoreBoundaryArg(maxLex, true),
-    ];
+    const args = [key, getLexBoundaryArg(minLex), getLexBoundaryArg(maxLex)];
     return createCommand(RequestType.ZLexCount, args);
 }
 
@@ -2016,6 +2062,55 @@ export function createXTrim(
     return createCommand(RequestType.XTrim, args);
 }
 
+/**
+ * @internal
+ */
+export function createXRange(
+    key: string,
+    start: Boundary,
+    end: Boundary,
+    count?: number,
+): command_request.Command {
+    const args = [key, getStreamBoundaryArg(start), getStreamBoundaryArg(end)];
+
+    if (count !== undefined) {
+        args.push("COUNT");
+        args.push(count.toString());
+    }
+
+    return createCommand(RequestType.XRange, args);
+}
+
+/**
+ * @internal
+ */
+export function createXGroupCreateConsumer(
+    key: string,
+    groupName: string,
+    consumerName: string,
+): command_request.Command {
+    return createCommand(RequestType.XGroupCreateConsumer, [
+        key,
+        groupName,
+        consumerName,
+    ]);
+}
+
+/**
+ * @internal
+ */
+export function createXGroupDelConsumer(
+    key: string,
+    groupName: string,
+    consumerName: string,
+): command_request.Command {
+    return createCommand(RequestType.XGroupDelConsumer, [
+        key,
+        groupName,
+        consumerName,
+    ]);
+}
+
 /**
  * @internal
  */
@@ -2149,6 +2244,24 @@ export function createFunctionList(
     return createCommand(RequestType.FunctionList, args);
 }
 
+/** Type of the response of `FUNCTION STATS` command. */
+export type FunctionStatsResponse = Record<
+    string,
+    | null
+    | Record
+    | Record>
+>;
+
+/** @internal */
+export function createFunctionStats(): command_request.Command {
+    return createCommand(RequestType.FunctionStats, []);
+}
+
+/** @internal */
+export function createFunctionKill(): command_request.Command {
+    return createCommand(RequestType.FunctionKill, []);
+}
+
 /**
  * Represents offsets specifying a string interval to analyze in the {@link BaseClient.bitcount|bitcount} command. The offsets are
  * zero-based indexes, with `0` being the first index of the string, `1` being the next index and so on.
@@ -2305,6 +2418,46 @@ export function createXRead(
     return createCommand(RequestType.XRead, args);
 }
 
+/**
+ * Represents a the return type for XInfo Stream in the response
+ */
+export type ReturnTypeXinfoStream = {
+    [key: string]:
+        | StreamEntries
+        | Record[]>[];
+};
+
+/**
+ * Represents an array of Stream Entires in the response
+ */
+export type StreamEntries = string | number | (string | number | string[])[][];
+
+/**
+ * @internal
+ */
+export function createXInfoStream(
+    key: string,
+    options: boolean | number,
+): command_request.Command {
+    const args: string[] = [key];
+
+    if (options != false) {
+        args.push("FULL");
+
+        if (typeof options === "number") {
+            args.push("COUNT");
+            args.push(options.toString());
+        }
+    }
+
+    return createCommand(RequestType.XInfoStream, args);
+}
+
+/** @internal */
+export function createXInfoGroups(key: string): command_request.Command {
+    return createCommand(RequestType.XInfoGroups, [key]);
+}
+
 /**
  * @internal
  */
@@ -2312,6 +2465,131 @@ export function createXLen(key: string): command_request.Command {
     return createCommand(RequestType.XLen, [key]);
 }
 
+/** Optional arguments for {@link BaseClient.xpendingWithOptions|xpending}. */
+export type StreamPendingOptions = {
+    /** Filter pending entries by their idle time - in milliseconds */
+    minIdleTime?: number;
+    /** Starting stream ID bound for range. */
+    start: Boundary;
+    /** Ending stream ID bound for range. */
+    end: Boundary;
+    /** Limit the number of messages returned. */
+    count: number;
+    /** Filter pending entries by consumer. */
+    consumer?: string;
+};
+
+/** @internal */
+export function createXPending(
+    key: string,
+    group: string,
+    options?: StreamPendingOptions,
+): command_request.Command {
+    const args = [key, group];
+
+    if (options) {
+        if (options.minIdleTime !== undefined)
+            args.push("IDLE", options.minIdleTime.toString());
+        args.push(
+            getStreamBoundaryArg(options.start),
+            getStreamBoundaryArg(options.end),
+            options.count.toString(),
+        );
+        if (options.consumer) args.push(options.consumer);
+    }
+
+    return createCommand(RequestType.XPending, args);
+}
+
+/** @internal */
+export function createXInfoConsumers(
+    key: string,
+    group: string,
+): command_request.Command {
+    return createCommand(RequestType.XInfoConsumers, [key, group]);
+}
+
+/** Optional parameters for {@link BaseClient.xclaim|xclaim} command. */
+export type StreamClaimOptions = {
+    /**
+     * Set the idle time (last time it was delivered) of the message in milliseconds. If `idle`
+     * is not specified, an `idle` of `0` is assumed, that is, the time count is reset
+     * because the message now has a new owner trying to process it.
+     */
+    idle?: number; // in milliseconds
+
+    /**
+     * This is the same as {@link idle} but instead of a relative amount of milliseconds, it sets the
+     * idle time to a specific Unix time (in milliseconds). This is useful in order to rewrite the AOF
+     * file generating `XCLAIM` commands.
+     */
+    idleUnixTime?: number; // in unix-time milliseconds
+
+    /**
+     * Set the retry counter to the specified value. This counter is incremented every time a message
+     * is delivered again. Normally {@link BaseClient.xclaim|xclaim} does not alter this counter,
+     * which is just served to clients when the {@link BaseClient.xpending|xpending} command is called:
+     * this way clients can detect anomalies, like messages that are never processed for some reason
+     * after a big number of delivery attempts.
+     */
+    retryCount?: number;
+
+    /**
+     * Creates the pending message entry in the PEL even if certain specified IDs are not already in
+     * the PEL assigned to a different client. However, the message must exist in the stream,
+     * otherwise the IDs of non-existing messages are ignored.
+     */
+    isForce?: boolean;
+};
+
+/** @internal */
+export function createXClaim(
+    key: string,
+    group: string,
+    consumer: string,
+    minIdleTime: number,
+    ids: string[],
+    options?: StreamClaimOptions,
+    justId?: boolean,
+): command_request.Command {
+    const args = [key, group, consumer, minIdleTime.toString(), ...ids];
+
+    if (options) {
+        if (options.idle !== undefined)
+            args.push("IDLE", options.idle.toString());
+        if (options.idleUnixTime !== undefined)
+            args.push("TIME", options.idleUnixTime.toString());
+        if (options.retryCount !== undefined)
+            args.push("RETRYCOUNT", options.retryCount.toString());
+        if (options.isForce) args.push("FORCE");
+    }
+
+    if (justId) args.push("JUSTID");
+    return createCommand(RequestType.XClaim, args);
+}
+
+/** @internal */
+export function createXAutoClaim(
+    key: string,
+    group: string,
+    consumer: string,
+    minIdleTime: number,
+    start: string,
+    count?: number,
+    justId?: boolean,
+): command_request.Command {
+    const args = [
+        key,
+        group,
+        consumer,
+        minIdleTime.toString(),
+        start.toString(),
+    ];
+    if (count !== undefined) args.push("COUNT", count.toString());
+    if (justId) args.push("JUSTID");
+    return createCommand(RequestType.XAutoClaim, args);
+}
+
 /**
  * Optional arguments for {@link BaseClient.xgroupCreate|xgroupCreate}.
  *
@@ -2454,6 +2732,12 @@ export type LolwutOptions = {
      *  For version `6`, those are number of columns and number of lines.
      */
     parameters?: number[];
+    /**
+     * An optional argument specifies the type of decoding.
+     *  Use Decoder.String to get the response as a String.
+     *  Use Decoder.Bytes to get the response in a buffer.
+     */
+    decoder?: Decoder;
 };
 
 /**
@@ -2521,6 +2805,87 @@ export function createCopy(
     return createCommand(RequestType.Copy, args);
 }
 
+/**
+ * @internal
+ */
+export function createMove(
+    key: string,
+    dbIndex: number,
+): command_request.Command {
+    return createCommand(RequestType.Move, [key, dbIndex.toString()]);
+}
+
+/**
+ * @internal
+ */
+export function createDump(key: GlideString): command_request.Command {
+    return createCommand(RequestType.Dump, [key]);
+}
+
+/**
+ * Optional arguments for `RESTORE` command.
+ *
+ * @See {@link https://valkey.io/commands/restore/|valkey.io} for details.
+ * @remarks `IDLETIME` and `FREQ` modifiers cannot be set at the same time.
+ */
+export type RestoreOptions = {
+    /**
+     * Set to `true` to replace the key if it exists.
+     */
+    replace?: boolean;
+    /**
+     * Set to `true` to specify that `ttl` argument of {@link BaseClient.restore} represents
+     * an absolute Unix timestamp (in milliseconds).
+     */
+    absttl?: boolean;
+    /**
+     * Set the `IDLETIME` option with object idletime to the given key.
+     */
+    idletime?: number;
+    /**
+     * Set the `FREQ` option with object frequency to the given key.
+     */
+    frequency?: number;
+};
+
+/**
+ * @internal
+ */
+export function createRestore(
+    key: GlideString,
+    ttl: number,
+    value: GlideString,
+    options?: RestoreOptions,
+): command_request.Command {
+    const args: GlideString[] = [key, ttl.toString(), value];
+
+    if (options) {
+        if (options.idletime !== undefined && options.frequency !== undefined) {
+            throw new Error(
+                `syntax error: both IDLETIME and FREQ cannot be set at the same time.`,
+            );
+        }
+
+        if (options.replace) {
+            args.push("REPLACE");
+        }
+
+        if (options.absttl) {
+            args.push("ABSTTL");
+        }
+
+        if (options.idletime !== undefined) {
+            args.push("IDLETIME", options.idletime.toString());
+        }
+
+        if (options.frequency !== undefined) {
+            args.push("FREQ", options.frequency.toString());
+        }
+    }
+
+    return createCommand(RequestType.Restore, args);
+}
+
 /**
  * Optional arguments to LPOS command.
  *
@@ -2699,7 +3064,7 @@ export function createGeoHash(
  * Optional parameters for {@link BaseClient.geosearch|geosearch} command which defines what should be included in the
  * search results and how results should be ordered and limited.
  */
-export type GeoSearchResultOptions = {
+export type GeoSearchResultOptions = GeoSearchCommonResultOptions & {
     /** Include the coordinate of the returned items. */
     withCoord?: boolean;
     /**
@@ -2709,6 +3074,22 @@ export type GeoSearchResultOptions = {
     withDist?: boolean;
     /** Include the geohash of the returned items. */
     withHash?: boolean;
+};
+
+/**
+ * Optional parameters for {@link BaseClient.geosearchstore|geosearchstore} command which defines what should be included in the
+ * search results and how results should be ordered and limited.
+ */
+export type GeoSearchStoreResultOptions = GeoSearchCommonResultOptions & {
+    /**
+     * Determines what is stored as the sorted set score. Defaults to `false`.
+     * - If set to `false`, the geohash of the location will be stored as the sorted set score.
+     * - If set to `true`, the distance from the center of the shape (circle or box) will be stored as the sorted set score. The distance is represented as a floating-point number in the same unit specified for that shape.
+     */
+    storeDist?: boolean;
+};
+
+type GeoSearchCommonResultOptions = {
     /** Indicates the order the result should be sorted in. */
     sortOrder?: SortOrder;
     /** Indicates the number of matches the result should be limited to. */
@@ -2759,16 +3140,39 @@ export type MemberOrigin = {
     member: string;
 };
 
-/**
- * @internal
- */
+/** @internal */
 export function createGeoSearch(
     key: string,
     searchFrom: SearchOrigin,
     searchBy: GeoSearchShape,
     resultOptions?: GeoSearchResultOptions,
 ): command_request.Command {
-    let args: string[] = [key];
+    const args = [key].concat(
+        convertGeoSearchOptionsToArgs(searchFrom, searchBy, resultOptions),
+    );
+    return createCommand(RequestType.GeoSearch, args);
+}
+
+/** @internal */
+export function createGeoSearchStore(
+    destination: string,
+    source: string,
+    searchFrom: SearchOrigin,
+    searchBy: GeoSearchShape,
+    resultOptions?: GeoSearchStoreResultOptions,
+): command_request.Command {
+    const args = [destination, source].concat(
+        convertGeoSearchOptionsToArgs(searchFrom, searchBy, resultOptions),
+    );
+    return createCommand(RequestType.GeoSearchStore, args);
+}
+
+function convertGeoSearchOptionsToArgs(
+    searchFrom: SearchOrigin,
+    searchBy: GeoSearchShape,
+    resultOptions?: GeoSearchCommonResultOptions,
+): string[] {
+    let args: string[] = [];
 
     if ("position" in searchFrom) {
         args = args.concat(
@@ -2796,9 +3200,14 @@ export function createGeoSearch(
     }
 
     if (resultOptions) {
-        if (resultOptions.withCoord) args.push("WITHCOORD");
-        if (resultOptions.withDist) args.push("WITHDIST");
-        if (resultOptions.withHash) args.push("WITHHASH");
+        if ("withCoord" in resultOptions && resultOptions.withCoord)
+            args.push("WITHCOORD");
+        if ("withDist" in resultOptions && resultOptions.withDist)
+            args.push("WITHDIST");
+        if ("withHash" in resultOptions && resultOptions.withHash)
+            args.push("WITHHASH");
+        if ("storeDist" in resultOptions && resultOptions.storeDist)
+            args.push("STOREDIST");
 
         if (resultOptions.count) {
             args.push("COUNT", resultOptions.count?.toString());
@@ -2809,7 +3218,7 @@ export function createGeoSearch(
         if (resultOptions.sortOrder) args.push(resultOptions.sortOrder);
     }
 
-    return createCommand(RequestType.GeoSearch, args);
+    return args;
 }
 
 /**
@@ -3035,6 +3444,35 @@ export function createHStrlen(
     return createCommand(RequestType.HStrlen, [key, field]);
 }
 
+/** @internal */
+export function createHRandField(
+    key: string,
+    count?: number,
+    withValues?: boolean,
+): command_request.Command {
+    const args = [key];
+    if (count !== undefined) args.push(count.toString());
+    if (withValues) args.push("WITHVALUES");
+    return createCommand(RequestType.HRandField, args);
+}
+
+/**
+ * @internal
+ */
+export function createHScan(
+    key: string,
+    cursor: string,
+    options?: BaseScanOptions,
+): command_request.Command {
+    let args: string[] = [key, cursor];
+
+    if (options) {
+        args = args.concat(convertBaseScanOptionsToArgsArray(options));
+    }
+
+    return createCommand(RequestType.HScan, args);
+}
+
 /**
  * @internal
  */
@@ -3107,6 +3545,17 @@ export function createUnWatch(): command_request.Command {
     return createCommand(RequestType.UnWatch, []);
 }
 
+/** @internal */
+export function createWait(
+    numreplicas: number,
+    timeout: number,
+): command_request.Command {
+    return createCommand(RequestType.Wait, [
+        numreplicas.toString(),
+        timeout.toString(),
+    ]);
+}
+
 /**
  * This base class represents the common set of optional arguments for the SCAN family of commands.
  * Concrete implementations of this class are tied to specific SCAN commands (SCAN, HSCAN, SSCAN,
@@ -3129,6 +3578,23 @@ export type BaseScanOptions = {
     readonly count?: number;
 };
 
+/**
+ * @internal
+ */
+function convertBaseScanOptionsToArgsArray(options: BaseScanOptions): string[] {
+    const args: string[] = [];
+
+    if (options.match) {
+        args.push("MATCH", options.match);
+    }
+
+    if (options.count !== undefined) {
+        args.push("COUNT", options.count.toString());
+    }
+
+    return args;
+}
+
 /**
  * @internal
  */
@@ -3140,13 +3606,7 @@ export function createZScan(
     let args: string[] = [key, cursor];
 
     if (options) {
-        if (options.match) {
-            args = args.concat("MATCH", options.match);
-        }
-
-        if (options.count !== undefined) {
-            args = args.concat("COUNT", options.count.toString());
-        }
+        args = args.concat(convertBaseScanOptionsToArgsArray(options));
     }
 
     return createCommand(RequestType.ZScan, args);
@@ -3161,6 +3621,14 @@ export function createSetRange(
     return createCommand(RequestType.SetRange, [key, offset.toString(), value]);
 }
 
+/** @internal */
+export function createAppend(
+    key: GlideString,
+    value: GlideString,
+): command_request.Command {
+    return createCommand(RequestType.Append, [key, value]);
+}
+
 /**
  * @internal
  */
@@ -3245,3 +3713,77 @@ export function createPubSubShardNumSub(
 ): command_request.Command {
     return createCommand(RequestType.PubSubSNumSub, channels ? channels : []);
 }
+
+/**
+ * @internal
+ */
+export function createBZPopMax(
+    keys: string[],
+    timeout: number,
+): command_request.Command {
+    return createCommand(RequestType.BZPopMax, [...keys, timeout.toString()]);
+}
+
+/**
+ * @internal
+ */
+export function createBZPopMin(
+    keys: string[],
+    timeout: number,
+): command_request.Command {
+    return createCommand(RequestType.BZPopMin, [...keys, timeout.toString()]);
+}
+
+/**
+ * Time unit representation which is used in optional arguments for {@link BaseClient.getex|getex} and {@link BaseClient.set|set} command.
+ */
+export enum TimeUnit {
+    /**
+     * Set the specified expire time, in seconds. Equivalent to
+     * `EX` in the VALKEY API.
+     */
+    Seconds = "EX",
+    /**
+     * Set the specified expire time, in milliseconds. Equivalent
+     * to `PX` in the VALKEY API.
+     */
+    Milliseconds = "PX",
+    /**
+     * Set the specified Unix time at which the key will expire,
+     * in seconds. Equivalent to `EXAT` in the VALKEY API.
+     */
+    UnixSeconds = "EXAT",
+    /**
+     * Set the specified Unix time at which the key will expire,
+     * in milliseconds. Equivalent to `PXAT` in the VALKEY API.
+     */
+    UnixMilliseconds = "PXAT",
+}
+
+/**
+ * @internal
+ */
+export function createGetEx(
+    key: string,
+    options?: "persist" | { type: TimeUnit; duration: number },
+): command_request.Command {
+    const args = [key];
+
+    if (options) {
+        if (options !== "persist" && !Number.isInteger(options.duration)) {
+            throw new Error(
+                `Received expiry '${JSON.stringify(
+                    options.duration,
+                )}'. Count must be an integer`,
+            );
+        }
+
+        if (options === "persist") {
+            args.push("PERSIST");
+        } else {
+            args.push(options.type, options.duration.toString());
+        }
+    }
+
+    return createCommand(RequestType.GetEx, args);
+}
diff --git a/node/src/GlideClient.ts b/node/src/GlideClient.ts
index 1c62b34506..e63f7f8fb1 100644
--- a/node/src/GlideClient.ts
+++ b/node/src/GlideClient.ts
@@ -6,6 +6,8 @@ import * as net from "net";
 import {
     BaseClient,
     BaseClientConfiguration,
+    Decoder,
+    GlideString,
     PubSubMsg,
     ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars
     ReturnType,
@@ -14,6 +16,7 @@ import {
     FlushMode,
     FunctionListOptions,
     FunctionListResponse,
+    FunctionStatsResponse,
     InfoOptions,
     LolwutOptions,
     SortOptions,
@@ -31,11 +34,14 @@ import {
     createFlushDB,
     createFunctionDelete,
     createFunctionFlush,
+    createFunctionKill,
     createFunctionList,
     createFunctionLoad,
+    createFunctionStats,
     createInfo,
     createLastSave,
     createLolwut,
+    createMove,
     createPing,
     createPublish,
     createRandomKey,
@@ -52,7 +58,7 @@ import { Transaction } from "./Transaction";
 export namespace GlideClientConfiguration {
     /**
      * Enum representing pubsub subscription modes.
-     * See [Valkey PubSub Documentation](https://valkey.io/docs/topics/pubsub/) for more details.
+     * @see {@link  https://valkey.io/docs/topics/pubsub/|Valkey PubSub Documentation} for more details.
      */
     export enum PubSubChannelModes {
         /**
@@ -125,8 +131,8 @@ export type GlideClientConfiguration = BaseClientConfiguration & {
 
 /**
  * Client used for connection to standalone Redis servers.
- * For full documentation, see
- * https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#standalone
+ *
+ * @see For full documentation refer to {@link https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#standalone|Valkey Glide Wiki}.
  */
 export class GlideClient extends BaseClient {
     /**
@@ -142,7 +148,7 @@ export class GlideClient extends BaseClient {
         return configuration;
     }
 
-    public static createClient(
+    public static async createClient(
         options: GlideClientConfiguration,
     ): Promise {
         return super.createClientInternal(
@@ -163,18 +169,25 @@ export class GlideClient extends BaseClient {
         );
     }
 
-    /** Execute a transaction by processing the queued commands.
-     *   See https://redis.io/topics/Transactions/ for details on Redis Transactions.
+    /**
+     * Execute a transaction by processing the queued commands.
+     *
+     * @see {@link https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#transaction|Valkey Glide Wiki} for details on Valkey Transactions.
      *
      * @param transaction - A Transaction object containing a list of commands to be executed.
+     * @param decoder - (Optional) {@link Decoder} type which defines how to handle the responses. If not set, the default decoder from the client config will be used.
      * @returns A list of results corresponding to the execution of each command in the transaction.
      *      If a command returns a value, it will be included in the list. If a command doesn't return a value,
      *      the list entry will be null.
      *      If the transaction failed due to a WATCH command, `exec` will return `null`.
      */
-    public exec(transaction: Transaction): Promise {
+    public async exec(
+        transaction: Transaction,
+        decoder: Decoder = this.defaultDecoder,
+    ): Promise {
         return this.createWritePromise(
             transaction.commands,
+            { decoder: decoder },
         ).then((result: ReturnType[] | null) => {
             return this.processResultWithSetCommands(
                 result,
@@ -186,8 +199,9 @@ export class GlideClient extends BaseClient {
     /** Executes a single command, without checking inputs. Every part of the command, including subcommands,
      *  should be added as a separate value in args.
      *
-     * See the [Glide for Redis Wiki](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command)
-     * for details on the restrictions and limitations of the custom command API.
+     * Note: An error will occur if the string decoder is used with commands that return only bytes as a response.
+     *
+     * @see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command|Valkey Glide Wiki} for details on the restrictions and limitations of the custom command API.
      *
      * @example
      * ```typescript
@@ -196,16 +210,22 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: Returns a list of all pub/sub clients
      * ```
      */
-    public customCommand(args: string[]): Promise {
-        return this.createWritePromise(createCustomCommand(args));
+    public async customCommand(
+        args: GlideString[],
+        decoder?: Decoder,
+    ): Promise {
+        return this.createWritePromise(createCustomCommand(args), {
+            decoder: decoder,
+        });
     }
 
     /** Ping the Redis server.
-     * See https://valkey.io/commands/ping/ for details.
+     * @see {@link https://valkey.io/commands/ping/|valkey.io} for details.
      *
      * @param message - An optional message to include in the PING command.
      * If not provided, the server will respond with "PONG".
      * If provided, the server will respond with a copy of the message.
+     * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. If not set, the default decoder from the client config will be used.
      * @returns - "PONG" if `message` is not provided, otherwise return a copy of `message`.
      *
      * @example
@@ -222,23 +242,28 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'Hello'
      * ```
      */
-    public ping(message?: string): Promise {
-        return this.createWritePromise(createPing(message));
+    public async ping(options?: {
+        message?: GlideString;
+        decoder?: Decoder;
+    }): Promise {
+        return this.createWritePromise(createPing(options?.message), {
+            decoder: options?.decoder,
+        });
     }
 
     /** Get information and statistics about the Redis server.
-     *  See https://valkey.io/commands/info/ for details.
+     * @see {@link https://valkey.io/commands/info/|valkey.io} for details.
      *
      * @param options - A list of InfoSection values specifying which sections of information to retrieve.
      *  When no parameter is provided, the default option is assumed.
      * @returns a string containing the information for the sections requested.
      */
-    public info(options?: InfoOptions[]): Promise {
+    public async info(options?: InfoOptions[]): Promise {
         return this.createWritePromise(createInfo(options));
     }
 
     /** Change the currently selected Redis database.
-     * See https://valkey.io/commands/select/ for details.
+     * @see {@link https://valkey.io/commands/select/|valkey.io} for details.
      *
      * @param index - The index of the database to select.
      * @returns A simple OK response.
@@ -250,12 +275,12 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public select(index: number): Promise<"OK"> {
+    public async select(index: number): Promise<"OK"> {
         return this.createWritePromise(createSelect(index));
     }
 
     /** Get the name of the primary's connection.
-     *  See https://valkey.io/commands/client-getname/ for more details.
+     * @see {@link https://valkey.io/commands/client-getname/|valkey.io} for more details.
      *
      * @returns the name of the client connection as a string if a name is set, or null if no name is assigned.
      *
@@ -266,12 +291,12 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'Client Name'
      * ```
      */
-    public clientGetName(): Promise {
+    public async clientGetName(): Promise {
         return this.createWritePromise(createClientGetName());
     }
 
     /** Rewrite the configuration file with the current configuration.
-     * See https://valkey.io/commands/config-rewrite/ for details.
+     * @see {@link https://valkey.io/commands/config-rewrite/|valkey.io} for details.
      *
      * @returns "OK" when the configuration was rewritten properly. Otherwise, an error is thrown.
      *
@@ -282,12 +307,13 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public configRewrite(): Promise<"OK"> {
+    public async configRewrite(): Promise<"OK"> {
         return this.createWritePromise(createConfigRewrite());
     }
 
     /** Resets the statistics reported by Redis using the INFO and LATENCY HISTOGRAM commands.
-     * See https://valkey.io/commands/config-resetstat/ for details.
+     *
+     * @see {@link https://valkey.io/commands/config-resetstat/|valkey.io} for details.
      *
      * @returns always "OK".
      *
@@ -298,21 +324,22 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public configResetStat(): Promise<"OK"> {
+    public async configResetStat(): Promise<"OK"> {
         return this.createWritePromise(createConfigResetStat());
     }
 
     /** Returns the current connection id.
-     * See https://valkey.io/commands/client-id/ for details.
+     * @see {@link https://valkey.io/commands/client-id/|valkey.io} for details.
      *
      * @returns the id of the client.
      */
-    public clientId(): Promise {
+    public async clientId(): Promise {
         return this.createWritePromise(createClientId());
     }
 
     /** Reads the configuration parameters of a running Redis server.
-     *  See https://valkey.io/commands/config-get/ for details.
+     *
+     * @see {@link https://valkey.io/commands/config-get/|valkey.io} for details.
      *
      * @param parameters - A list of configuration parameter names to retrieve values for.
      *
@@ -325,15 +352,17 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: {'timeout': '1000', 'maxmemory': '1GB'}
      * ```
      */
-    public configGet(parameters: string[]): Promise> {
+    public async configGet(
+        parameters: string[],
+    ): Promise> {
         return this.createWritePromise(createConfigGet(parameters));
     }
 
-    /** Set configuration parameters to the specified values.
-     *   See https://valkey.io/commands/config-set/ for details.
+    /**
+     * Set configuration parameters to the specified values.
      *
+     * @see {@link  https://valkey.io/commands/config-set/|valkey.io} for details.
      * @param parameters - A List of keyValuePairs consisting of configuration parameters and their respective values to set.
-     *
      * @returns "OK" when the configuration was set properly. Otherwise an error is thrown.
      *
      * @example
@@ -343,12 +372,12 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public configSet(parameters: Record): Promise<"OK"> {
+    public async configSet(parameters: Record): Promise<"OK"> {
         return this.createWritePromise(createConfigSet(parameters));
     }
 
     /** Echoes the provided `message` back.
-     * See https://valkey.io/commands/echo for more details.
+     * @see {@link https://valkey.io/commands/echo|valkey.io} for more details.
      *
      * @param message - The message to be echoed back.
      * @returns The provided `message`.
@@ -360,12 +389,12 @@ export class GlideClient extends BaseClient {
      * console.log(echoedMessage); // Output: 'valkey-glide'
      * ```
      */
-    public echo(message: string): Promise {
+    public async echo(message: string): Promise {
         return this.createWritePromise(createEcho(message));
     }
 
     /** Returns the server time
-     * See https://valkey.io/commands/time/ for details.
+     * @see {@link https://valkey.io/commands/time/|valkey.io} for details.
      *
      * @returns - The current server time as a two items `array`:
      * A Unix timestamp and the amount of microseconds already elapsed in the current second.
@@ -378,7 +407,7 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: ['1710925775', '913580']
      * ```
      */
-    public time(): Promise<[string, string]> {
+    public async time(): Promise<[string, string]> {
         return this.createWritePromise(createTime());
     }
 
@@ -388,7 +417,8 @@ export class GlideClient extends BaseClient {
      * When `replace` is true, removes the `destination` key first if it already exists, otherwise performs
      * no action.
      *
-     * See https://valkey.io/commands/copy/ for more details.
+     * @see {@link https://valkey.io/commands/copy/|valkey.io} for more details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param source - The key to the source value.
      * @param destination - The key where the value should be copied to.
@@ -398,8 +428,6 @@ export class GlideClient extends BaseClient {
      *     value to it. If not provided, no action will be performed if the key already exists.
      * @returns `true` if `source` was copied, `false` if the `source` was not copied.
      *
-     * since Valkey version 6.2.0.
-     *
      * @example
      * ```typescript
      * const result = await client.copy("set1", "set2");
@@ -424,10 +452,30 @@ export class GlideClient extends BaseClient {
         );
     }
 
+    /**
+     * Move `key` from the currently selected database to the database specified by `dbIndex`.
+     *
+     * @see {@link https://valkey.io/commands/move/|valkey.io} for more details.
+     *
+     * @param key - The key to move.
+     * @param dbIndex - The index of the database to move `key` to.
+     * @returns `true` if `key` was moved, or `false` if the `key` already exists in the destination
+     *     database or does not exist in the source database.
+     *
+     * @example
+     * ```typescript
+     * const result = await client.move("key", 1);
+     * console.log(result); // Output: true
+     * ```
+     */
+    public async move(key: string, dbIndex: number): Promise {
+        return this.createWritePromise(createMove(key, dbIndex));
+    }
+
     /**
      * Displays a piece of generative computer art and the server version.
      *
-     * See https://valkey.io/commands/lolwut/ for more details.
+     * @see {@link https://valkey.io/commands/lolwut/|valkey.io} for more details.
      *
      * @param options - The LOLWUT options
      * @returns A piece of generative computer art along with the current server version.
@@ -438,16 +486,15 @@ export class GlideClient extends BaseClient {
      * console.log(response); // Output: "Redis ver. 7.2.3" - Indicates the current server version.
      * ```
      */
-    public lolwut(options?: LolwutOptions): Promise {
+    public async lolwut(options?: LolwutOptions): Promise {
         return this.createWritePromise(createLolwut(options));
     }
 
     /**
      * Deletes a library and all its functions.
      *
-     * See https://valkey.io/commands/function-delete/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-delete/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param libraryCode - The library name to delete.
      * @returns A simple OK response.
@@ -458,16 +505,15 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public functionDelete(libraryCode: string): Promise {
+    public async functionDelete(libraryCode: string): Promise {
         return this.createWritePromise(createFunctionDelete(libraryCode));
     }
 
     /**
      * Loads a library to Valkey.
      *
-     * See https://valkey.io/commands/function-load/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-load/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param libraryCode - The source code that implements the library.
      * @param replace - Whether the given library should overwrite a library with the same name if it
@@ -481,7 +527,7 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'mylib'
      * ```
      */
-    public functionLoad(
+    public async functionLoad(
         libraryCode: string,
         replace?: boolean,
     ): Promise {
@@ -493,9 +539,8 @@ export class GlideClient extends BaseClient {
     /**
      * Deletes all function libraries.
      *
-     * See https://valkey.io/commands/function-flush/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-flush/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param mode - The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}.
      * @returns A simple OK response.
@@ -506,16 +551,15 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public functionFlush(mode?: FlushMode): Promise {
+    public async functionFlush(mode?: FlushMode): Promise {
         return this.createWritePromise(createFunctionFlush(mode));
     }
 
     /**
      * Returns information about the functions and libraries.
      *
-     * See https://valkey.io/commands/function-list/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-list/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param options - Parameters to filter and request additional info.
      * @returns Info about all or selected libraries and their functions in {@link FunctionListResponse} format.
@@ -545,10 +589,78 @@ export class GlideClient extends BaseClient {
         return this.createWritePromise(createFunctionList(options));
     }
 
+    /**
+     * Returns information about the function that's currently running and information about the
+     * available execution engines.
+     *
+     * @see {@link https://valkey.io/commands/function-stats/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
+     *
+     * @returns A `Record` with two keys:
+     *     - `"running_script"` with information about the running script.
+     *     - `"engines"` with information about available engines and their stats.
+     *     - see example for more details.
+     *
+     * @example
+     * ```typescript
+     * const response = await client.functionStats();
+     * console.log(response); // Output:
+     * // {
+     * //     "running_script":
+     * //     {
+     * //         "name": "deep_thought",
+     * //         "command": ["fcall", "deep_thought", "0"],
+     * //         "duration_ms": 5008
+     * //     },
+     * //     "engines":
+     * //     {
+     * //         "LUA":
+     * //         {
+     * //             "libraries_count": 2,
+     * //             "functions_count": 3
+     * //         }
+     * //     }
+     * // }
+     * // Output if no scripts running:
+     * // {
+     * //     "running_script": null
+     * //     "engines":
+     * //     {
+     * //         "LUA":
+     * //         {
+     * //             "libraries_count": 2,
+     * //             "functions_count": 3
+     * //         }
+     * //     }
+     * // }
+     * ```
+     */
+    public async functionStats(): Promise {
+        return this.createWritePromise(createFunctionStats());
+    }
+
+    /**
+     * Kills a function that is currently executing.
+     * `FUNCTION KILL` terminates read-only functions only.
+     *
+     * See https://valkey.io/commands/function-kill/ for details.
+     *
+     * since Valkey version 7.0.0.
+     *
+     * @returns `OK` if function is terminated. Otherwise, throws an error.
+     * @example
+     * ```typescript
+     * await client.functionKill();
+     * ```
+     */
+    public async functionKill(): Promise<"OK"> {
+        return this.createWritePromise(createFunctionKill());
+    }
+
     /**
      * Deletes all the keys of all the existing databases. This command never fails.
      *
-     * See https://valkey.io/commands/flushall/ for more details.
+     * @see {@link https://valkey.io/commands/flushall/|valkey.io} for more details.
      *
      * @param mode - The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}.
      * @returns `OK`.
@@ -559,14 +671,14 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public flushall(mode?: FlushMode): Promise {
+    public async flushall(mode?: FlushMode): Promise {
         return this.createWritePromise(createFlushAll(mode));
     }
 
     /**
      * Deletes all the keys of the currently selected database. This command never fails.
      *
-     * See https://valkey.io/commands/flushdb/ for more details.
+     * @see {@link https://valkey.io/commands/flushdb/|valkey.io} for more details.
      *
      * @param mode - The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}.
      * @returns `OK`.
@@ -577,14 +689,14 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public flushdb(mode?: FlushMode): Promise {
+    public async flushdb(mode?: FlushMode): Promise {
         return this.createWritePromise(createFlushDB(mode));
     }
 
     /**
      * Returns the number of keys in the currently selected database.
      *
-     * See https://valkey.io/commands/dbsize/ for more details.
+     * @see {@link https://valkey.io/commands/dbsize/|valkey.io} for more details.
      *
      * @returns The number of keys in the currently selected database.
      *
@@ -594,13 +706,13 @@ export class GlideClient extends BaseClient {
      * console.log("Number of keys in the current database: ", numKeys);
      * ```
      */
-    public dbsize(): Promise {
+    public async dbsize(): Promise {
         return this.createWritePromise(createDBSize());
     }
 
     /** Publish a message on pubsub channel.
      *
-     * See https://valkey.io/commands/publish for more details.
+     * @see {@link https://valkey.io/commands/publish/|valkey.io} for more details.
      *
      * @param message - Message to publish.
      * @param channel - Channel to publish the message on.
@@ -614,7 +726,7 @@ export class GlideClient extends BaseClient {
      * console.log(result); // Output: 1 - This message was posted to 1 subscription which is configured on primary node
      * ```
      */
-    public publish(message: string, channel: string): Promise {
+    public async publish(message: string, channel: string): Promise {
         return this.createWritePromise(createPublish(message, channel));
     }
 
@@ -626,7 +738,7 @@ export class GlideClient extends BaseClient {
      *
      * To store the result into a new key, see {@link sortStore}.
      *
-     * See https://valkey.io/commands/sort for more details.
+     * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details.
      *
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param options - The {@link SortOptions}.
@@ -656,7 +768,8 @@ export class GlideClient extends BaseClient {
      *
      * This command is routed depending on the client's {@link ReadFrom} strategy.
      *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param options - The {@link SortOptions}.
@@ -687,9 +800,9 @@ export class GlideClient extends BaseClient {
      *
      * To get the sort result without storing it into a key, see {@link sort} or {@link sortReadOnly}.
      *
-     * See https://valkey.io/commands/sort for more details.
-     *
+     * @see {@link https://valkey.io/commands/sort|valkey.io} for more details.
      * @remarks When in cluster mode, `destination` and `key` must map to the same hash slot.
+     *
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param destination - The key where the sorted result will be stored.
      * @param options - The {@link SortOptions}.
@@ -717,7 +830,7 @@ export class GlideClient extends BaseClient {
      * Returns `UNIX TIME` of the last DB save timestamp or startup timestamp if no save
      * was made since then.
      *
-     * See https://valkey.io/commands/lastsave/ for more details.
+     * @see {@link https://valkey.io/commands/lastsave/|valkey.io} for more details.
      *
      * @returns `UNIX TIME` of the last DB save executed with success.
      * @example
@@ -733,7 +846,7 @@ export class GlideClient extends BaseClient {
     /**
      * Returns a random existing key name from the currently selected database.
      *
-     * See https://valkey.io/commands/randomkey/ for more details.
+     * @see {@link https://valkey.io/commands/randomkey/|valkey.io} for more details.
      *
      * @returns A random existing key name from the currently selected database.
      *
@@ -751,7 +864,7 @@ export class GlideClient extends BaseClient {
      * Flushes all the previously watched keys for a transaction. Executing a transaction will
      * automatically flush all previously watched keys.
      *
-     * See https://valkey.io/commands/unwatch/ and https://valkey.io/topics/transactions/#cas for more details.
+     * @see {@link https://valkey.io/commands/unwatch/|valkey.io} and {@link https://valkey.io/topics/transactions/#cas|Valkey Glide Wiki} for more details.
      *
      * @returns A simple "OK" response.
      *
diff --git a/node/src/GlideClusterClient.ts b/node/src/GlideClusterClient.ts
index 14f5e0f7fb..0aeb430ae4 100644
--- a/node/src/GlideClusterClient.ts
+++ b/node/src/GlideClusterClient.ts
@@ -6,6 +6,8 @@ import * as net from "net";
 import {
     BaseClient,
     BaseClientConfiguration,
+    Decoder,
+    GlideString,
     PubSubMsg,
     ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars
     ReturnType,
@@ -14,6 +16,7 @@ import {
     FlushMode,
     FunctionListOptions,
     FunctionListResponse,
+    FunctionStatsResponse,
     InfoOptions,
     LolwutOptions,
     SortClusterOptions,
@@ -33,8 +36,10 @@ import {
     createFlushDB,
     createFunctionDelete,
     createFunctionFlush,
+    createFunctionKill,
     createFunctionList,
     createFunctionLoad,
+    createFunctionStats,
     createInfo,
     createLastSave,
     createLolwut,
@@ -83,7 +88,7 @@ export type PeriodicChecks =
 export namespace GlideClusterClientConfiguration {
     /**
      * Enum representing pubsub subscription modes.
-     * See [Valkey PubSub Documentation](https://valkey.io/docs/topics/pubsub/) for more details.
+     * @see {@link https://valkey.io/docs/topics/pubsub/|Valkey PubSub Documentation} for more details.
      */
     export enum PubSubChannelModes {
         /**
@@ -280,8 +285,8 @@ function toProtobufRoute(
 
 /**
  * Client used for connection to cluster Redis servers.
- * For full documentation, see
- * https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#cluster
+ *
+ * @see For full documentation refer to {@link https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#cluster|Valkey Glide Wiki}.
  */
 export class GlideClusterClient extends BaseClient {
     /**
@@ -339,23 +344,31 @@ export class GlideClusterClient extends BaseClient {
      *  The command will be routed automatically based on the passed command's default request policy, unless `route` is provided,
      *  in which case the client will route the command to the nodes defined by `route`.
      *
-     * See the [Glide for Redis Wiki](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command)
-     * for details on the restrictions and limitations of the custom command API.
+     * Note: An error will occur if the string decoder is used with commands that return only bytes as a response.
+     *
+     * @see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command|Glide for Valkey Wiki} for details on the restrictions and limitations of the custom command API.
      *
      * @example
      * ```typescript
      * // Example usage of customCommand method to retrieve pub/sub clients with routing to all primary nodes
-     * const result = await client.customCommand(["CLIENT", "LIST", "TYPE", "PUBSUB"], "allPrimaries");
+     * const result = await client.customCommand(["CLIENT", "LIST", "TYPE", "PUBSUB"], {route: "allPrimaries", decoder: Decoder.String});
      * console.log(result); // Output: Returns a list of all pub/sub clients
      * ```
      */
-    public customCommand(args: string[], route?: Routes): Promise {
+    public async customCommand(
+        args: GlideString[],
+        options?: { route?: Routes; decoder?: Decoder },
+    ): Promise> {
         const command = createCustomCommand(args);
-        return super.createWritePromise(command, toProtobufRoute(route));
+        return super.createWritePromise(command, {
+            route: toProtobufRoute(options?.route),
+            decoder: options?.decoder,
+        });
     }
 
-    /** Execute a transaction by processing the queued commands.
-     *   See https://redis.io/topics/Transactions/ for details on Redis Transactions.
+    /**
+     * Execute a transaction by processing the queued commands.
+     * @see {@link https://redis.io/topics/Transactions/|Valkey Glide Wiki} for details on Redis Transactions.
      *
      * @param transaction - A ClusterTransaction object containing a list of commands to be executed.
      * @param route - If `route` is not provided, the transaction will be routed to the slot owner of the first key found in the transaction.
@@ -366,13 +379,19 @@ export class GlideClusterClient extends BaseClient {
      *      the list entry will be null.
      *      If the transaction failed due to a WATCH command, `exec` will return `null`.
      */
-    public exec(
+    public async exec(
         transaction: ClusterTransaction,
-        route?: SingleNodeRoute,
+        options?: {
+            route?: SingleNodeRoute;
+            decoder?: Decoder;
+        },
     ): Promise {
         return this.createWritePromise(
             transaction.commands,
-            toProtobufRoute(route),
+            {
+                route: toProtobufRoute(options?.route),
+                decoder: options?.decoder,
+            },
         ).then((result: ReturnType[] | null) => {
             return this.processResultWithSetCommands(
                 result,
@@ -382,13 +401,15 @@ export class GlideClusterClient extends BaseClient {
     }
 
     /** Ping the Redis server.
-     * See https://valkey.io/commands/ping/ for details.
+     *
+     * @see {@link https://valkey.io/commands/ping/|valkey.io} for details.
      *
      * @param message - An optional message to include in the PING command.
      * If not provided, the server will respond with "PONG".
      * If provided, the server will respond with a copy of the message.
      * @param route - The command will be routed to all primaries, unless `route` is provided, in which
      *   case the client will route the command to the nodes defined by `route`.
+     * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. If not set, the default decoder from the client config will be used.
      * @returns - "PONG" if `message` is not provided, otherwise return a copy of `message`.
      *
      * @example
@@ -405,15 +426,19 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: 'Hello'
      * ```
      */
-    public ping(message?: string, route?: Routes): Promise {
-        return this.createWritePromise(
-            createPing(message),
-            toProtobufRoute(route),
-        );
+    public async ping(options?: {
+        message?: GlideString;
+        route?: Routes;
+        decoder?: Decoder;
+    }): Promise {
+        return this.createWritePromise(createPing(options?.message), {
+            route: toProtobufRoute(options?.route),
+            decoder: options?.decoder,
+        });
     }
 
     /** Get information and statistics about the Redis server.
-     *  See https://valkey.io/commands/info/ for details.
+     * @see {@link https://valkey.io/commands/info/|valkey.io} for details.
      *
      * @param options - A list of InfoSection values specifying which sections of information to retrieve.
      *  When no parameter is provided, the default option is assumed.
@@ -422,18 +447,18 @@ export class GlideClusterClient extends BaseClient {
      * @returns a string containing the information for the sections requested. When specifying a route other than a single node,
      * it returns a dictionary where each address is the key and its corresponding node response is the value.
      */
-    public info(
+    public async info(
         options?: InfoOptions[],
         route?: Routes,
     ): Promise> {
         return this.createWritePromise>(
             createInfo(options),
-            toProtobufRoute(route),
+            { route: toProtobufRoute(route) },
         );
     }
 
     /** Get the name of the connection to which the request is routed.
-     *  See https://valkey.io/commands/client-getname/ for more details.
+     * @see {@link https://valkey.io/commands/client-getname/|valkey.io} for details.
      *
      * @param route - The command will be routed a random node, unless `route` is provided, in which
      *   case the client will route the command to the nodes defined by `route`.
@@ -456,21 +481,20 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: {'addr': 'Connection Name', 'addr2': 'Connection Name', 'addr3': 'Connection Name'}
      * ```
      */
-    public clientGetName(
+    public async clientGetName(
         route?: Routes,
     ): Promise> {
         return this.createWritePromise>(
             createClientGetName(),
-            toProtobufRoute(route),
+            { route: toProtobufRoute(route) },
         );
     }
 
     /** Rewrite the configuration file with the current configuration.
-     * See https://valkey.io/commands/config-rewrite/ for details.
+     * @see {@link https://valkey.io/commands/config-rewrite/|valkey.io} for details.
      *
      * @param route - The command will be routed to all nodes, unless `route` is provided, in which
      *   case the client will route the command to the nodes defined by `route`.
-     *
      * @returns "OK" when the configuration was rewritten properly. Otherwise, an error is thrown.
      *
      * @example
@@ -480,19 +504,17 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public configRewrite(route?: Routes): Promise<"OK"> {
-        return this.createWritePromise(
-            createConfigRewrite(),
-            toProtobufRoute(route),
-        );
+    public async configRewrite(route?: Routes): Promise<"OK"> {
+        return this.createWritePromise(createConfigRewrite(), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /** Resets the statistics reported by Redis using the INFO and LATENCY HISTOGRAM commands.
-     * See https://valkey.io/commands/config-resetstat/ for details.
+     * @see {@link https://valkey.io/commands/config-resetstat/|valkey.io} for details.
      *
      * @param route - The command will be routed to all nodes, unless `route` is provided, in which
      *   case the client will route the command to the nodes defined by `route`.
-     *
      * @returns always "OK".
      *
      * @example
@@ -502,30 +524,29 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public configResetStat(route?: Routes): Promise<"OK"> {
-        return this.createWritePromise(
-            createConfigResetStat(),
-            toProtobufRoute(route),
-        );
+    public async configResetStat(route?: Routes): Promise<"OK"> {
+        return this.createWritePromise(createConfigResetStat(), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /** Returns the current connection id.
-     * See https://valkey.io/commands/client-id/ for details.
+     * @see {@link https://valkey.io/commands/client-id/|valkey.io} for details.
      *
      * @param route - The command will be routed to a random node, unless `route` is provided, in which
      *   case the client will route the command to the nodes defined by `route`.
      * @returns the id of the client. When specifying a route other than a single node,
      * it returns a dictionary where each address is the key and its corresponding node response is the value.
      */
-    public clientId(route?: Routes): Promise> {
+    public async clientId(route?: Routes): Promise> {
         return this.createWritePromise>(
             createClientId(),
-            toProtobufRoute(route),
+            { route: toProtobufRoute(route) },
         );
     }
 
     /** Reads the configuration parameters of a running Redis server.
-     *  See https://valkey.io/commands/config-get/ for details.
+     * @see {@link https://valkey.io/commands/config-get/|valkey.io} for details.
      *
      * @param parameters - A list of configuration parameter names to retrieve values for.
      * @param route - The command will be routed to a random node, unless `route` is provided, in which
@@ -549,24 +570,23 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: {'timeout': '1000', 'maxmemory': '1GB'}
      * ```
      */
-    public configGet(
+    public async configGet(
         parameters: string[],
         route?: Routes,
     ): Promise>> {
         return this.createWritePromise>>(
             createConfigGet(parameters),
-            toProtobufRoute(route),
+            { route: toProtobufRoute(route) },
         );
     }
 
     /** Set configuration parameters to the specified values.
-     *   See https://valkey.io/commands/config-set/ for details.
+     * @see {@link https://valkey.io/commands/config-set/|valkey.io} for details.
      *
      * @param parameters - A List of keyValuePairs consisting of configuration parameters and their respective values to set.
      * @param route - The command will be routed to all nodes, unless `route` is provided, in which
      *   case the client will route the command to the nodes defined by `route`.
      *   If `route` is not provided, the command will be sent to the all nodes.
-     *
      * @returns "OK" when the configuration was set properly. Otherwise an error is thrown.
      *
      * @example
@@ -576,18 +596,17 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public configSet(
+    public async configSet(
         parameters: Record,
         route?: Routes,
     ): Promise<"OK"> {
-        return this.createWritePromise(
-            createConfigSet(parameters),
-            toProtobufRoute(route),
-        );
+        return this.createWritePromise(createConfigSet(parameters), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /** Echoes the provided `message` back.
-     * See https://valkey.io/commands/echo for more details.
+     * @see {@link https://valkey.io/commands/echo/|valkey.io} for details.
      *
      * @param message - The message to be echoed back.
      * @param route - The command will be routed to a random node, unless `route` is provided, in which
@@ -608,18 +627,17 @@ export class GlideClusterClient extends BaseClient {
      * console.log(echoedMessage); // Output: {'addr': 'valkey-glide', 'addr2': 'valkey-glide', 'addr3': 'valkey-glide'}
      * ```
      */
-    public echo(
+    public async echo(
         message: string,
         route?: Routes,
     ): Promise> {
-        return this.createWritePromise(
-            createEcho(message),
-            toProtobufRoute(route),
-        );
+        return this.createWritePromise(createEcho(message), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /** Returns the server time.
-     * See https://valkey.io/commands/time/ for details.
+     * @see {@link https://valkey.io/commands/time/|valkey.io} for details.
      *
      * @param route - The command will be routed to a random node, unless `route` is provided, in which
      *  case the client will route the command to the nodes defined by `route`.
@@ -644,25 +662,28 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: {'addr': ['1710925775', '913580'], 'addr2': ['1710925775', '913580'], 'addr3': ['1710925775', '913580']}
      * ```
      */
-    public time(route?: Routes): Promise> {
-        return this.createWritePromise(createTime(), toProtobufRoute(route));
+    public async time(
+        route?: Routes,
+    ): Promise> {
+        return this.createWritePromise(createTime(), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Copies the value stored at the `source` to the `destination` key. When `replace` is `true`,
      * removes the `destination` key first if it already exists, otherwise performs no action.
      *
-     * See https://valkey.io/commands/copy/ for more details.
-     *
+     * @see {@link https://valkey.io/commands/copy/|valkey.io} for details.
      * @remarks When in cluster mode, `source` and `destination` must map to the same hash slot.
+     * @remarks Since Valkey version 6.2.0.
+     *
      * @param source - The key to the source value.
      * @param destination - The key where the value should be copied to.
      * @param replace - (Optional) If `true`, the `destination` key should be removed before copying the
      *     value to it. If not provided, no action will be performed if the key already exists.
      * @returns `true` if `source` was copied, `false` if the `source` was not copied.
      *
-     * since Valkey version 6.2.0.
-     *
      * @example
      * ```typescript
      * const result = await client.copy("set1", "set2", true);
@@ -682,7 +703,7 @@ export class GlideClusterClient extends BaseClient {
     /**
      * Displays a piece of generative computer art and the server version.
      *
-     * See https://valkey.io/commands/lolwut/ for more details.
+     * @see {@link https://valkey.io/commands/lolwut/|valkey.io} for details.
      *
      * @param options - The LOLWUT options.
      * @param route - The command will be routed to a random node, unless `route` is provided, in which
@@ -695,22 +716,21 @@ export class GlideClusterClient extends BaseClient {
      * console.log(response); // Output: "Redis ver. 7.2.3" - Indicates the current server version.
      * ```
      */
-    public lolwut(
+    public async lolwut(
         options?: LolwutOptions,
         route?: Routes,
     ): Promise> {
-        return this.createWritePromise(
-            createLolwut(options),
-            toProtobufRoute(route),
-        );
+        return this.createWritePromise(createLolwut(options), {
+            decoder: options?.decoder,
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Invokes a previously loaded function.
      *
-     * See https://valkey.io/commands/fcall/ for more details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/fcall/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param func - The function name.
      * @param args - A list of `function` arguments and it should not represent names of keys.
@@ -724,23 +744,21 @@ export class GlideClusterClient extends BaseClient {
      * console.log(response); // Output: Returns the function's return value.
      * ```
      */
-    public fcallWithRoute(
+    public async fcallWithRoute(
         func: string,
         args: string[],
         route?: Routes,
     ): Promise {
-        return this.createWritePromise(
-            createFCall(func, [], args),
-            toProtobufRoute(route),
-        );
+        return this.createWritePromise(createFCall(func, [], args), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Invokes a previously loaded read-only function.
      *
-     * See https://valkey.io/commands/fcall/ for more details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/fcall/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param func - The function name.
      * @param args - A list of `function` arguments and it should not represent names of keys.
@@ -755,23 +773,21 @@ export class GlideClusterClient extends BaseClient {
      * console.log(response); // Output: 42 # The return value on the function that was execute.
      * ```
      */
-    public fcallReadonlyWithRoute(
+    public async fcallReadonlyWithRoute(
         func: string,
         args: string[],
         route?: Routes,
     ): Promise {
-        return this.createWritePromise(
-            createFCallReadOnly(func, [], args),
-            toProtobufRoute(route),
-        );
+        return this.createWritePromise(createFCallReadOnly(func, [], args), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Deletes a library and all its functions.
      *
-     * See https://valkey.io/commands/function-delete/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-delete/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param libraryCode - The library name to delete.
      * @param route - The command will be routed to all primary node, unless `route` is provided, in which
@@ -784,22 +800,20 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public functionDelete(
+    public async functionDelete(
         libraryCode: string,
         route?: Routes,
     ): Promise {
-        return this.createWritePromise(
-            createFunctionDelete(libraryCode),
-            toProtobufRoute(route),
-        );
+        return this.createWritePromise(createFunctionDelete(libraryCode), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Loads a library to Valkey.
      *
-     * See https://valkey.io/commands/function-load/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-load/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param libraryCode - The source code that implements the library.
      * @param replace - Whether the given library should overwrite a library with the same name if it
@@ -815,26 +829,25 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: 'mylib'
      * ```
      */
-    public functionLoad(
+    public async functionLoad(
         libraryCode: string,
         replace?: boolean,
         route?: Routes,
     ): Promise {
         return this.createWritePromise(
             createFunctionLoad(libraryCode, replace),
-            toProtobufRoute(route),
+            { route: toProtobufRoute(route) },
         );
     }
 
     /**
      * Deletes all function libraries.
      *
-     * See https://valkey.io/commands/function-flush/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-flush/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param mode - The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}.
-     * @param route - The command will be routed to all primary node, unless `route` is provided, in which
+     * @param route - The command will be routed to all primary nodes, unless `route` is provided, in which
      *   case the client will route the command to the nodes defined by `route`.
      * @returns A simple OK response.
      *
@@ -844,19 +857,20 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public functionFlush(mode?: FlushMode, route?: Routes): Promise {
-        return this.createWritePromise(
-            createFunctionFlush(mode),
-            toProtobufRoute(route),
-        );
+    public async functionFlush(
+        mode?: FlushMode,
+        route?: Routes,
+    ): Promise {
+        return this.createWritePromise(createFunctionFlush(mode), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Returns information about the functions and libraries.
      *
-     * See https://valkey.io/commands/function-list/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-list/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param options - Parameters to filter and request additional info.
      * @param route - The client will route the command to the nodes defined by `route`.
@@ -886,16 +900,93 @@ export class GlideClusterClient extends BaseClient {
         options?: FunctionListOptions,
         route?: Routes,
     ): Promise> {
-        return this.createWritePromise(
-            createFunctionList(options),
-            toProtobufRoute(route),
-        );
+        return this.createWritePromise(createFunctionList(options), {
+            route: toProtobufRoute(route),
+        });
+    }
+
+    /**
+     * Returns information about the function that's currently running and information about the
+     * available execution engines.
+     *
+     * @see {@link https://valkey.io/commands/function-stats/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
+     *
+     * @param route - The client will route the command to the nodes defined by `route`.
+     *     If not defined, the command will be routed to all primary nodes.
+     * @returns A `Record` with two keys:
+     *     - `"running_script"` with information about the running script.
+     *     - `"engines"` with information about available engines and their stats.
+     *     - See example for more details.
+     *
+     * @example
+     * ```typescript
+     * const response = await client.functionStats("randomNode");
+     * console.log(response); // Output:
+     * // {
+     * //     "running_script":
+     * //     {
+     * //         "name": "deep_thought",
+     * //         "command": ["fcall", "deep_thought", "0"],
+     * //         "duration_ms": 5008
+     * //     },
+     * //     "engines":
+     * //     {
+     * //         "LUA":
+     * //         {
+     * //             "libraries_count": 2,
+     * //             "functions_count": 3
+     * //         }
+     * //     }
+     * // }
+     * // Output if no scripts running:
+     * // {
+     * //     "running_script": null
+     * //     "engines":
+     * //     {
+     * //         "LUA":
+     * //         {
+     * //             "libraries_count": 2,
+     * //             "functions_count": 3
+     * //         }
+     * //     }
+     * // }
+     * ```
+     */
+    public async functionStats(
+        route?: Routes,
+    ): Promise> {
+        return this.createWritePromise(createFunctionStats(), {
+            route: toProtobufRoute(route),
+        });
+    }
+
+    /**
+     * Kills a function that is currently executing.
+     * `FUNCTION KILL` terminates read-only functions only.
+     *
+     * @see {@link https://valkey.io/commands/function-kill/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
+     *
+     * @param route - (Optional) The client will route the command to the nodes defined by `route`.
+     *     If not defined, the command will be routed to all primary nodes.
+     * @returns `OK` if function is terminated. Otherwise, throws an error.
+     *
+     * @example
+     * ```typescript
+     * await client.functionKill();
+     * ```
+     */
+    public async functionKill(route?: Routes): Promise<"OK"> {
+        return this.createWritePromise(createFunctionKill(), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Deletes all the keys of all the existing databases. This command never fails.
      *
-     * See https://valkey.io/commands/flushall/ for more details.
+     * @see {@link https://valkey.io/commands/flushall/|valkey.io} for details.
      *
      * @param mode - The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}.
      * @param route - The command will be routed to all primary nodes, unless `route` is provided, in which
@@ -908,17 +999,16 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public flushall(mode?: FlushMode, route?: Routes): Promise {
-        return this.createWritePromise(
-            createFlushAll(mode),
-            toProtobufRoute(route),
-        );
+    public async flushall(mode?: FlushMode, route?: Routes): Promise {
+        return this.createWritePromise(createFlushAll(mode), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Deletes all the keys of the currently selected database. This command never fails.
      *
-     * See https://valkey.io/commands/flushdb/ for more details.
+     * @see {@link https://valkey.io/commands/flushdb/|valkey.io} for details.
      *
      * @param mode - The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}.
      * @param route - The command will be routed to all primary nodes, unless `route` is provided, in which
@@ -931,17 +1021,16 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: 'OK'
      * ```
      */
-    public flushdb(mode?: FlushMode, route?: Routes): Promise {
-        return this.createWritePromise(
-            createFlushDB(mode),
-            toProtobufRoute(route),
-        );
+    public async flushdb(mode?: FlushMode, route?: Routes): Promise {
+        return this.createWritePromise(createFlushDB(mode), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Returns the number of keys in the database.
      *
-     * See https://valkey.io/commands/dbsize/ for more details.
+     * @see {@link https://valkey.io/commands/dbsize/|valkey.io} for details.
 
      * @param route - The command will be routed to all primary nodes, unless `route` is provided, in which
      *     case the client will route the command to the nodes defined by `route`.
@@ -954,8 +1043,10 @@ export class GlideClusterClient extends BaseClient {
      * console.log("Number of keys across all primary nodes: ", numKeys);
      * ```
      */
-    public dbsize(route?: Routes): Promise {
-        return this.createWritePromise(createDBSize(), toProtobufRoute(route));
+    public async dbsize(route?: Routes): Promise> {
+        return this.createWritePromise(createDBSize(), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /** Publish a message on pubsub channel.
@@ -963,7 +1054,7 @@ export class GlideClusterClient extends BaseClient {
      * The mode is selected using the 'sharded' parameter.
      * For both sharded and non-sharded mode, request is routed using hashed channel as key.
      *
-     * See https://valkey.io/commands/publish and https://valkey.io/commands/spublish for more details.
+     * @see {@link https://valkey.io/commands/publish} and {@link https://valkey.io/commands/spublish} for more details.
      *
      * @param message - Message to publish.
      * @param channel - Channel to publish the message on.
@@ -984,7 +1075,7 @@ export class GlideClusterClient extends BaseClient {
      * console.log(result); // Output: 2 - Published 2 instances of "Hi to sharded channel1!" message on channel1 using sharded mode
      * ```
      */
-    public publish(
+    public async publish(
         message: string,
         channel: string,
         sharded: boolean = false,
@@ -998,7 +1089,7 @@ export class GlideClusterClient extends BaseClient {
      * Lists the currently active shard channels.
      * The command is routed to all nodes, and aggregates the response to a single array.
      *
-     * See https://valkey.io/commands/pubsub-shardchannels for more details.
+     * @see {@link https://valkey.io/commands/pubsub-shardchannels/|valkey.io} for details.
      *
      * @param pattern - A glob-style pattern to match active shard channels.
      *                  If not provided, all active shard channels are returned.
@@ -1024,7 +1115,7 @@ export class GlideClusterClient extends BaseClient {
      * Note that it is valid to call this command without channels. In this case, it will just return an empty map.
      * The command is routed to all nodes, and aggregates the response to a single map of the channels and their number of subscriptions.
      *
-     * See https://valkey.io/commands/pubsub-shardnumsub for more details.
+     * @see {@link https://valkey.io/commands/pubsub-shardnumsub/|valkey.io} for details.
      *
      * @param channels - The list of shard channels to query for the number of subscribers.
      *                   If not provided, returns an empty map.
@@ -1053,7 +1144,7 @@ export class GlideClusterClient extends BaseClient {
      *
      * To store the result into a new key, see {@link sortStore}.
      *
-     * See https://valkey.io/commands/sort for more details.
+     * @see {@link https://valkey.io/commands/sort/|valkey.io} for details.
      *
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param options - (Optional) {@link SortClusterOptions}.
@@ -1081,7 +1172,7 @@ export class GlideClusterClient extends BaseClient {
      *
      * This command is routed depending on the client's {@link ReadFrom} strategy.
      *
-     * since Valkey version 7.0.0.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param options - (Optional) {@link SortClusterOptions}.
@@ -1110,9 +1201,9 @@ export class GlideClusterClient extends BaseClient {
      *
      * To get the sort result without storing it into a key, see {@link sort} or {@link sortReadOnly}.
      *
-     * See https://valkey.io/commands/sort for more details.
-     *
+     * @see {@link https://valkey.io/commands/sort/|valkey.io} for details.
      * @remarks When in cluster mode, `destination` and `key` must map to the same hash slot.
+     *
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param destination - The key where the sorted result will be stored.
      * @param options - (Optional) {@link SortClusterOptions}.
@@ -1138,7 +1229,7 @@ export class GlideClusterClient extends BaseClient {
      * Returns `UNIX TIME` of the last DB save timestamp or startup timestamp if no save
      * was made since then.
      *
-     * See https://valkey.io/commands/lastsave/ for more details.
+     * @see {@link https://valkey.io/commands/lastsave/|valkey.io} for details.
      *
      * @param route - (Optional) The command will be routed to a random node, unless `route` is provided, in which
      *     case the client will route the command to the nodes defined by `route`.
@@ -1150,16 +1241,15 @@ export class GlideClusterClient extends BaseClient {
      * ```
      */
     public async lastsave(route?: Routes): Promise> {
-        return this.createWritePromise(
-            createLastSave(),
-            toProtobufRoute(route),
-        );
+        return this.createWritePromise(createLastSave(), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Returns a random existing key name.
      *
-     * See https://valkey.io/commands/randomkey/ for more details.
+     * @see {@link https://valkey.io/commands/randomkey/|valkey.io} for details.
      *
      * @param route - (Optional) The command will be routed to all primary nodes, unless `route` is provided,
      *      in which case the client will route the command to the nodes defined by `route`.
@@ -1172,17 +1262,16 @@ export class GlideClusterClient extends BaseClient {
      * ```
      */
     public async randomKey(route?: Routes): Promise {
-        return this.createWritePromise(
-            createRandomKey(),
-            toProtobufRoute(route),
-        );
+        return this.createWritePromise(createRandomKey(), {
+            route: toProtobufRoute(route),
+        });
     }
 
     /**
      * Flushes all the previously watched keys for a transaction. Executing a transaction will
      * automatically flush all previously watched keys.
      *
-     * See https://valkey.io/commands/unwatch/ and https://valkey.io/topics/transactions/#cas for more details.
+     * @see {@link https://valkey.io/commands/unwatch/|valkey.io} and {@link https://valkey.io/topics/transactions/#cas|Valkey Glide Wiki} for more details.
      *
      * @param route - (Optional) The command will be routed to all primary nodes, unless `route` is provided,
      *      in which case the client will route the command to the nodes defined by `route`.
@@ -1197,6 +1286,8 @@ export class GlideClusterClient extends BaseClient {
      * ```
      */
     public async unwatch(route?: Routes): Promise<"OK"> {
-        return this.createWritePromise(createUnWatch(), toProtobufRoute(route));
+        return this.createWritePromise(createUnWatch(), {
+            route: toProtobufRoute(route),
+        });
     }
 }
diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts
index 2795f85d2e..986aabff9b 100644
--- a/node/src/Transaction.ts
+++ b/node/src/Transaction.ts
@@ -3,6 +3,8 @@
  */
 
 import {
+    BaseClient, // eslint-disable-line @typescript-eslint/no-unused-vars
+    GlideString,
     ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars
 } from "./BaseClient";
 
@@ -19,16 +21,19 @@ import {
     BitOffsetOptions,
     BitmapIndexType,
     BitwiseOperation,
+    Boundary,
     CoordOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars
     ExpireOptions,
     FlushMode,
     FunctionListOptions,
     FunctionListResponse, // eslint-disable-line @typescript-eslint/no-unused-vars
+    FunctionStatsResponse, // eslint-disable-line @typescript-eslint/no-unused-vars
     GeoAddOptions,
     GeoBoxShape, // eslint-disable-line @typescript-eslint/no-unused-vars
     GeoCircleShape, // eslint-disable-line @typescript-eslint/no-unused-vars
     GeoSearchResultOptions,
     GeoSearchShape,
+    GeoSearchStoreResultOptions,
     GeoUnit,
     GeospatialData,
     InfoOptions,
@@ -41,22 +46,28 @@ import {
     RangeByIndex,
     RangeByLex,
     RangeByScore,
-    ScoreBoundary,
+    ReturnTypeXinfoStream, // eslint-disable-line @typescript-eslint/no-unused-vars
     ScoreFilter,
     SearchOrigin,
     SetOptions,
     SortClusterOptions,
     SortOptions,
     StreamAddOptions,
+    StreamClaimOptions,
     StreamGroupOptions,
+    StreamPendingOptions,
     StreamReadOptions,
     StreamTrimOptions,
+    TimeUnit,
     ZAddOptions,
+    createAppend,
     createBLMPop,
     createBLMove,
     createBLPop,
     createBRPop,
     createBZMPop,
+    createBZPopMax,
+    createBZPopMin,
     createBitCount,
     createBitField,
     createBitOp,
@@ -86,14 +97,17 @@ import {
     createFunctionFlush,
     createFunctionList,
     createFunctionLoad,
+    createFunctionStats,
     createGeoAdd,
     createGeoDist,
     createGeoHash,
     createGeoPos,
     createGeoSearch,
+    createGeoSearchStore,
     createGet,
     createGetBit,
     createGetDel,
+    createGetEx,
     createGetRange,
     createHDel,
     createHExists,
@@ -101,8 +115,11 @@ import {
     createHGetAll,
     createHIncrBy,
     createHIncrByFloat,
+    createHKeys,
     createHLen,
     createHMGet,
+    createHRandField,
+    createHScan,
     createHSet,
     createHSetNX,
     createHStrlen,
@@ -130,6 +147,7 @@ import {
     createMGet,
     createMSet,
     createMSetNX,
+    createMove,
     createObjectEncoding,
     createObjectFreq,
     createObjectIdletime,
@@ -167,7 +185,9 @@ import {
     createSMembers,
     createSMove,
     createSPop,
+    createSRandMember,
     createSRem,
+    createSScan,
     createSUnion,
     createSUnionStore,
     createSelect,
@@ -182,13 +202,23 @@ import {
     createTouch,
     createType,
     createUnlink,
+    createWait,
     createXAdd,
+    createXAutoClaim,
+    createXClaim,
     createXDel,
+    createXGroupCreate,
+    createXGroupCreateConsumer,
+    createXGroupDelConsumer,
+    createXGroupDestroy,
+    createXInfoConsumers,
+    createXInfoGroups,
+    createXInfoStream,
     createXLen,
+    createXPending,
+    createXRange,
     createXRead,
     createXTrim,
-    createXGroupCreate,
-    createXGroupDestroy,
     createZAdd,
     createZCard,
     createZCount,
@@ -205,6 +235,7 @@ import {
     createZPopMin,
     createZRandMember,
     createZRange,
+    createZRangeStore,
     createZRangeWithScores,
     createZRank,
     createZRem,
@@ -268,26 +299,46 @@ export class BaseTransaction> {
     }
 
     /** Get the value associated with the given key, or null if no such value exists.
-     * See https://valkey.io/commands/get/ for details.
+     * @see {@link https://valkey.io/commands/get/|valkey.io} for details.
      *
      * @param key - The key to retrieve from the database.
      *
-     * Command Response - If `key` exists, returns the value of `key` as a string. Otherwise, return null.
+     * Command Response - If `key` exists, returns the value of `key`. Otherwise, return null.
      */
-    public get(key: string): T {
+    public get(key: GlideString): T {
         return this.addAndReturn(createGet(key));
     }
 
+    /**
+     * Get the value of `key` and optionally set its expiration. `GETEX` is similar to {@link get}.
+     *
+     * @see {@link https://valkey.io/commands/getex/|valkey.io} for more details.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param key - The key to retrieve from the database.
+     * @param options - (Optional) set expiriation to the given key.
+     *                  "persist" will retain the time to live associated with the key. Equivalent to `PERSIST` in the VALKEY API.
+     *                  Otherwise, a {@link TimeUnit} and duration of the expire time should be specified.
+     *
+     * Command Response - If `key` exists, returns the value of `key` as a `string`. Otherwise, return `null`.
+     */
+    public getex(
+        key: string,
+        options?: "persist" | { type: TimeUnit; duration: number },
+    ): T {
+        return this.addAndReturn(createGetEx(key, options));
+    }
+
     /**
      * Gets a string value associated with the given `key`and deletes the key.
      *
-     * See https://valkey.io/commands/getdel/ for details.
+     * @see {@link https://valkey.io/commands/getdel/|valkey.io} for details.
      *
      * @param key - The key to retrieve from the database.
      *
      * Command Response - If `key` exists, returns the `value` of `key`. Otherwise, return `null`.
      */
-    public getdel(key: string): T {
+    public getdel(key: GlideString): T {
         return this.addAndReturn(createGetDel(key));
     }
 
@@ -298,7 +349,7 @@ export class BaseTransaction> {
      * penultimate and so forth. If `key` does not exist, an empty string is returned. If `start`
      * or `end` are out of range, returns the substring within the valid range of the string.
      *
-     * See https://valkey.io/commands/getrange/ for details.
+     * @see {@link https://valkey.io/commands/getrange/|valkey.io} for details.
      *
      * @param key - The key of the string.
      * @param start - The starting offset.
@@ -311,7 +362,7 @@ export class BaseTransaction> {
     }
 
     /** Set the given key with the given value. Return value is dependent on the passed options.
-     * See https://valkey.io/commands/set/ for details.
+     * @see {@link https://valkey.io/commands/set/|valkey.io} for details.
      *
      * @param key - The key to store.
      * @param value - The value to store with the given key.
@@ -326,7 +377,7 @@ export class BaseTransaction> {
     }
 
     /** Ping the Redis server.
-     * See https://valkey.io/commands/ping/ for details.
+     * @see {@link https://valkey.io/commands/ping/|valkey.io} for details.
      *
      * @param message - An optional message to include in the PING command.
      * If not provided, the server will respond with "PONG".
@@ -334,12 +385,12 @@ export class BaseTransaction> {
      *
      * Command Response - "PONG" if `message` is not provided, otherwise return a copy of `message`.
      */
-    public ping(message?: string): T {
+    public ping(message?: GlideString): T {
         return this.addAndReturn(createPing(message));
     }
 
     /** Get information and statistics about the Redis server.
-     * See https://valkey.io/commands/info/ for details.
+     * @see {@link https://valkey.io/commands/info/|valkey.io} for details.
      *
      * @param options - A list of InfoSection values specifying which sections of information to retrieve.
      * When no parameter is provided, the default option is assumed.
@@ -351,7 +402,7 @@ export class BaseTransaction> {
     }
 
     /** Remove the specified keys. A key is ignored if it does not exist.
-     * See https://valkey.io/commands/del/ for details.
+     * @see {@link https://valkey.io/commands/del/|valkey.io} for details.
      *
      * @param keys - A list of keys to be deleted from the database.
      *
@@ -362,7 +413,7 @@ export class BaseTransaction> {
     }
 
     /** Get the name of the connection on which the transaction is being executed.
-     * See https://valkey.io/commands/client-getname/ for more details.
+     * @see {@link https://valkey.io/commands/client-getname/|valkey.io} for details.
      *
      * Command Response - the name of the client connection as a string if a name is set, or null if no name is assigned.
      */
@@ -371,7 +422,7 @@ export class BaseTransaction> {
     }
 
     /** Rewrite the configuration file with the current configuration.
-     * See https://valkey.io/commands/select/ for details.
+     * @see {@link https://valkey.io/commands/select/|valkey.io} for details.
      *
      * Command Response - "OK" when the configuration was rewritten properly. Otherwise, the transaction fails with an error.
      */
@@ -380,7 +431,7 @@ export class BaseTransaction> {
     }
 
     /** Resets the statistics reported by Redis using the INFO and LATENCY HISTOGRAM commands.
-     * See https://valkey.io/commands/config-resetstat/ for details.
+     * @see {@link https://valkey.io/commands/config-resetstat/|valkey.io} for details.
      *
      * Command Response - always "OK".
      */
@@ -389,7 +440,7 @@ export class BaseTransaction> {
     }
 
     /** Retrieve the values of multiple keys.
-     * See https://valkey.io/commands/mget/ for details.
+     * @see {@link https://valkey.io/commands/mget/|valkey.io} for details.
      *
      * @param keys - A list of keys to retrieve values for.
      *
@@ -401,7 +452,7 @@ export class BaseTransaction> {
     }
 
     /** Set multiple keys to multiple values in a single atomic operation.
-     * See https://valkey.io/commands/mset/ for details.
+     * @see {@link https://valkey.io/commands/mset/|valkey.io} for details.
      *
      * @param keyValueMap - A key-value map consisting of keys and their respective values to set.
      *
@@ -415,7 +466,7 @@ export class BaseTransaction> {
      * Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or
      * more keys already exist, the entire operation fails.
      *
-     * See https://valkey.io/commands/msetnx/ for more details.
+     * @see {@link https://valkey.io/commands/msetnx/|valkey.io} for details.
      *
      * @param keyValueMap - A key-value map consisting of keys and their respective values to set.
      * Command Response - `true` if all keys were set. `false` if no key was set.
@@ -425,7 +476,7 @@ export class BaseTransaction> {
     }
 
     /** Increments the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation.
-     * See https://valkey.io/commands/incr/ for details.
+     * @see {@link https://valkey.io/commands/incr/|valkey.io} for details.
      *
      * @param key - The key to increment its value.
      *
@@ -436,7 +487,7 @@ export class BaseTransaction> {
     }
 
     /** Increments the number stored at `key` by `amount`. If `key` does not exist, it is set to 0 before performing the operation.
-     * See https://valkey.io/commands/incrby/ for details.
+     * @see {@link https://valkey.io/commands/incrby/|valkey.io} for details.
      *
      * @param key - The key to increment its value.
      * @param amount - The amount to increment.
@@ -450,7 +501,7 @@ export class BaseTransaction> {
     /** Increment the string representing a floating point number stored at `key` by `amount`.
      * By using a negative amount value, the result is that the value stored at `key` is decremented.
      * If `key` does not exist, it is set to 0 before performing the operation.
-     * See https://valkey.io/commands/incrbyfloat/ for details.
+     * @see {@link https://valkey.io/commands/incrbyfloat/|valkey.io} for details.
      *
      * @param key - The key to increment its value.
      * @param amount - The amount to increment.
@@ -463,7 +514,7 @@ export class BaseTransaction> {
     }
 
     /** Returns the current connection id.
-     * See https://valkey.io/commands/client-id/ for details.
+     * @see {@link https://valkey.io/commands/client-id/|valkey.io} for details.
      *
      * Command Response - the id of the client.
      */
@@ -472,7 +523,7 @@ export class BaseTransaction> {
     }
 
     /** Decrements the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation.
-     * See https://valkey.io/commands/decr/ for details.
+     * @see {@link https://valkey.io/commands/decr/|valkey.io} for details.
      *
      * @param key - The key to decrement its value.
      *
@@ -483,7 +534,7 @@ export class BaseTransaction> {
     }
 
     /** Decrements the number stored at `key` by `amount`. If `key` does not exist, it is set to 0 before performing the operation.
-     * See https://valkey.io/commands/decrby/ for details.
+     * @see {@link https://valkey.io/commands/decrby/|valkey.io} for details.
      *
      * @param key - The key to decrement its value.
      * @param amount - The amount to decrement.
@@ -498,7 +549,7 @@ export class BaseTransaction> {
      * Perform a bitwise operation between multiple keys (containing string values) and store the result in the
      * `destination`.
      *
-     * See https://valkey.io/commands/bitop/ for more details.
+     * @see {@link https://valkey.io/commands/bitop/|valkey.io} for details.
      *
      * @param operation - The bitwise operation to perform.
      * @param destination - The key that will store the resulting string.
@@ -518,7 +569,7 @@ export class BaseTransaction> {
      * Returns the bit value at `offset` in the string value stored at `key`. `offset` must be greater than or equal
      * to zero.
      *
-     * See https://valkey.io/commands/getbit/ for more details.
+     * @see {@link https://valkey.io/commands/getbit/|valkey.io} for details.
      *
      * @param key - The key of the string.
      * @param offset - The index of the bit to return.
@@ -536,7 +587,7 @@ export class BaseTransaction> {
      * `2^32` and greater than or equal to `0`. If a key is non-existent then the bit at `offset` is set to `value` and
      * the preceding bits are set to `0`.
      *
-     * See https://valkey.io/commands/setbit/ for more details.
+     * @see {@link https://valkey.io/commands/setbit/|valkey.io} for details.
      *
      * @param key - The key of the string.
      * @param offset - The index of the bit to be set.
@@ -554,7 +605,7 @@ export class BaseTransaction> {
      * The offset can also be a negative number indicating an offset starting at the end of the list, with `-1` being
      * the last byte of the list, `-2` being the penultimate, and so on.
      *
-     * See https://valkey.io/commands/bitpos/ for more details.
+     * @see {@link https://valkey.io/commands/bitpos/|valkey.io} for details.
      *
      * @param key - The key of the string.
      * @param bit - The bit value to match. Must be `0` or `1`.
@@ -578,7 +629,7 @@ export class BaseTransaction> {
      * are assumed. If BIT is specified, `start=0` and `end=2` means to look at the first three bits. If BYTE is
      * specified, `start=0` and `end=2` means to look at the first three bytes.
      *
-     * See https://valkey.io/commands/bitpos/ for more details.
+     * @see {@link https://valkey.io/commands/bitpos/|valkey.io} for details.
      *
      * @param key - The key of the string.
      * @param bit - The bit value to match. Must be `0` or `1`.
@@ -605,7 +656,7 @@ export class BaseTransaction> {
      * Reads or modifies the array of bits representing the string that is held at `key` based on the specified
      * `subcommands`.
      *
-     * See https://valkey.io/commands/bitfield/ for more details.
+     * @see {@link https://valkey.io/commands/bitfield/|valkey.io} for details.
      *
      * @param key - The key of the string.
      * @param subcommands - The subcommands to be performed on the binary value of the string at `key`, which could be
@@ -632,21 +683,21 @@ export class BaseTransaction> {
     /**
      * Reads the array of bits representing the string that is held at `key` based on the specified `subcommands`.
      *
-     * See https://valkey.io/commands/bitfield_ro/ for more details.
+     * @see {@link https://valkey.io/commands/bitfield_ro/|valkey.io} for details.
+     * @remarks Since Valkey version 6.0.0.
      *
      * @param key - The key of the string.
      * @param subcommands - The {@link BitFieldGet} subcommands to be performed.
      *
      * Command Response - An array of results from the {@link BitFieldGet} subcommands.
      *
-     * since Valkey version 6.0.0.
      */
     public bitfieldReadOnly(key: string, subcommands: BitFieldGet[]): T {
         return this.addAndReturn(createBitField(key, subcommands, true));
     }
 
     /** Reads the configuration parameters of a running Redis server.
-     * See https://valkey.io/commands/config-get/ for details.
+     * @see {@link https://valkey.io/commands/config-get/|valkey.io} for details.
      *
      * @param parameters - A list of configuration parameter names to retrieve values for.
      *
@@ -658,7 +709,7 @@ export class BaseTransaction> {
     }
 
     /** Set configuration parameters to the specified values.
-     * See https://valkey.io/commands/config-set/ for details.
+     * @see {@link https://valkey.io/commands/config-set/|valkey.io} for details.
      *
      * @param parameters - A List of keyValuePairs consisting of configuration parameters and their respective values to set.
      *
@@ -669,7 +720,7 @@ export class BaseTransaction> {
     }
 
     /** Retrieve the value associated with `field` in the hash stored at `key`.
-     * See https://valkey.io/commands/hget/ for details.
+     * @see {@link https://valkey.io/commands/hget/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      * @param field - The field in the hash stored at `key` to retrieve from the database.
@@ -681,7 +732,7 @@ export class BaseTransaction> {
     }
 
     /** Sets the specified fields to their respective values in the hash stored at `key`.
-     * See https://valkey.io/commands/hset/ for details.
+     * @see {@link https://valkey.io/commands/hset/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      * @param fieldValueMap - A field-value map consisting of fields and their corresponding values
@@ -693,10 +744,23 @@ export class BaseTransaction> {
         return this.addAndReturn(createHSet(key, fieldValueMap));
     }
 
+    /**
+     * Returns all field names in the hash stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/hkeys/|valkey.io} for details.
+     *
+     * @param key - The key of the hash.
+     *
+     * Command Response - A list of field names for the hash, or an empty list when the key does not exist.
+     */
+    public hkeys(key: string): T {
+        return this.addAndReturn(createHKeys(key));
+    }
+
     /** Sets `field` in the hash stored at `key` to `value`, only if `field` does not yet exist.
      * If `key` does not exist, a new key holding a hash is created.
      * If `field` already exists, this operation has no effect.
-     * See https://valkey.io/commands/hsetnx/ for more details.
+     * @see {@link https://valkey.io/commands/hsetnx/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      * @param field - The field to set the value for.
@@ -710,7 +774,7 @@ export class BaseTransaction> {
 
     /** Removes the specified fields from the hash stored at `key`.
      * Specified fields that do not exist within this hash are ignored.
-     * See https://valkey.io/commands/hdel/ for details.
+     * @see {@link https://valkey.io/commands/hdel/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      * @param fields - The fields to remove from the hash stored at `key`.
@@ -723,7 +787,7 @@ export class BaseTransaction> {
     }
 
     /** Returns the values associated with the specified fields in the hash stored at `key`.
-     * See https://valkey.io/commands/hmget/ for details.
+     * @see {@link https://valkey.io/commands/hmget/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      * @param fields - The fields in the hash stored at `key` to retrieve from the database.
@@ -737,7 +801,7 @@ export class BaseTransaction> {
     }
 
     /** Returns if `field` is an existing field in the hash stored at `key`.
-     * See https://valkey.io/commands/hexists/ for details.
+     * @see {@link https://valkey.io/commands/hexists/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      * @param field - The field to check in the hash stored at `key`.
@@ -750,7 +814,7 @@ export class BaseTransaction> {
     }
 
     /** Returns all fields and values of the hash stored at `key`.
-     * See https://valkey.io/commands/hgetall/ for details.
+     * @see {@link https://valkey.io/commands/hgetall/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      *
@@ -764,7 +828,7 @@ export class BaseTransaction> {
     /** Increments the number stored at `field` in the hash stored at `key` by `increment`.
      * By using a negative increment value, the value stored at `field` in the hash stored at `key` is decremented.
      * If `field` or `key` does not exist, it is set to 0 before performing the operation.
-     * See https://valkey.io/commands/hincrby/ for details.
+     * @see {@link https://valkey.io/commands/hincrby/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      * @param amount - The amount to increment.
@@ -779,7 +843,7 @@ export class BaseTransaction> {
     /** Increment the string representing a floating point number stored at `field` in the hash stored at `key` by `increment`.
      * By using a negative increment value, the value stored at `field` in the hash stored at `key` is decremented.
      * If `field` or `key` does not exist, it is set to 0 before performing the operation.
-     * See https://valkey.io/commands/hincrbyfloat/ for details.
+     * @see {@link https://valkey.io/commands/hincrbyfloat/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      * @param amount - The amount to increment.
@@ -792,7 +856,7 @@ export class BaseTransaction> {
     }
 
     /** Returns the number of fields contained in the hash stored at `key`.
-     * See https://valkey.io/commands/hlen/ for more details.
+     * @see {@link https://valkey.io/commands/hlen/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      *
@@ -803,7 +867,7 @@ export class BaseTransaction> {
     }
 
     /** Returns all values in the hash stored at key.
-     * See https://valkey.io/commands/hvals/ for more details.
+     * @see {@link https://valkey.io/commands/hvals/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      *
@@ -816,7 +880,7 @@ export class BaseTransaction> {
     /**
      * Returns the string length of the value associated with `field` in the hash stored at `key`.
      *
-     * See https://valkey.io/commands/hstrlen/ for details.
+     * @see {@link https://valkey.io/commands/hstrlen/|valkey.io} for details.
      *
      * @param key - The key of the hash.
      * @param field - The field in the hash.
@@ -827,10 +891,82 @@ export class BaseTransaction> {
         return this.addAndReturn(createHStrlen(key, field));
     }
 
+    /**
+     * Returns a random field name from the hash value stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param key - The key of the hash.
+     *
+     * Command Response - A random field name from the hash stored at `key`, or `null` when
+     *     the key does not exist.
+     */
+    public hrandfield(key: string): T {
+        return this.addAndReturn(createHRandField(key));
+    }
+
+    /**
+     * Iterates incrementally over a hash.
+     *
+     * @see {@link https://valkey.io/commands/hscan/|valkey.io} for more details.
+     *
+     * @param key - The key of the set.
+     * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of the search.
+     * @param options - (Optional) The {@link BaseScanOptions}.
+     *
+     * Command Response -  An array of the `cursor` and the subset of the hash held by `key`.
+     * The first element is always the `cursor` for the next iteration of results. `"0"` will be the `cursor`
+     * returned on the last iteration of the hash. The second element is always an array of the subset of the
+     * hash held in `key`. The array in the second element is always a flattened series of string pairs,
+     * where the value is at even indices and the value is at odd indices.
+     */
+    public hscan(key: string, cursor: string, options?: BaseScanOptions): T {
+        return this.addAndReturn(createHScan(key, cursor, options));
+    }
+
+    /**
+     * Retrieves up to `count` random field names from the hash value stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param key - The key of the hash.
+     * @param count - The number of field names to return.
+     *
+     *     If `count` is positive, returns unique elements. If negative, allows for duplicates.
+     *
+     * Command Response - An `array` of random field names from the hash stored at `key`,
+     *     or an `empty array` when the key does not exist.
+     */
+    public hrandfieldCount(key: string, count: number): T {
+        return this.addAndReturn(createHRandField(key, count));
+    }
+
+    /**
+     * Retrieves up to `count` random field names along with their values from the hash
+     * value stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param key - The key of the hash.
+     * @param count - The number of field names to return.
+     *
+     *     If `count` is positive, returns unique elements. If negative, allows for duplicates.
+     *
+     * Command Response - A 2D `array` of `[fieldName, value]` `arrays`, where `fieldName` is a random
+     *     field name from the hash and `value` is the associated value of the field name.
+     *     If the hash does not exist or is empty, the response will be an empty `array`.
+     */
+    public hrandfieldWithValues(key: string, count: number): T {
+        return this.addAndReturn(createHRandField(key, count, true));
+    }
+
     /** Inserts all the specified values at the head of the list stored at `key`.
      * `elements` are inserted one after the other to the head of the list, from the leftmost element to the rightmost element.
      * If `key` does not exist, it is created as empty list before performing the push operations.
-     * See https://valkey.io/commands/lpush/ for details.
+     * @see {@link https://valkey.io/commands/lpush/|valkey.io} for details.
      *
      * @param key - The key of the list.
      * @param elements - The elements to insert at the head of the list stored at `key`.
@@ -845,7 +981,7 @@ export class BaseTransaction> {
      * Inserts specified values at the head of the `list`, only if `key` already
      * exists and holds a list.
      *
-     * See https://valkey.io/commands/lpushx/ for details.
+     * @see {@link https://valkey.io/commands/lpushx/|valkey.io} for details.
      *
      * @param key - The key of the list.
      * @param elements - The elements to insert at the head of the list stored at `key`.
@@ -858,7 +994,7 @@ export class BaseTransaction> {
 
     /** Removes and returns the first elements of the list stored at `key`.
      * The command pops a single element from the beginning of the list.
-     * See https://valkey.io/commands/lpop/ for details.
+     * @see {@link https://valkey.io/commands/lpop/|valkey.io} for details.
      *
      * @param key - The key of the list.
      *
@@ -870,7 +1006,7 @@ export class BaseTransaction> {
     }
 
     /** Removes and returns up to `count` elements of the list stored at `key`, depending on the list's length.
-     * See https://valkey.io/commands/lpop/ for details.
+     * @see {@link https://valkey.io/commands/lpop/|valkey.io} for details.
      *
      * @param key - The key of the list.
      * @param count - The count of the elements to pop from the list.
@@ -886,7 +1022,7 @@ export class BaseTransaction> {
      * The offsets `start` and `end` are zero-based indexes, with 0 being the first element of the list, 1 being the next element and so on.
      * These offsets can also be negative numbers indicating offsets starting at the end of the list,
      * with -1 being the last element of the list, -2 being the penultimate, and so on.
-     * See https://valkey.io/commands/lrange/ for details.
+     * @see {@link https://valkey.io/commands/lrange/|valkey.io} for details.
      *
      * @param key - The key of the list.
      * @param start - The starting point of the range.
@@ -902,7 +1038,7 @@ export class BaseTransaction> {
     }
 
     /** Returns the length of the list stored at `key`.
-     * See https://valkey.io/commands/llen/ for details.
+     * @see {@link https://valkey.io/commands/llen/|valkey.io} for details.
      *
      * @param key - The key of the list.
      *
@@ -918,7 +1054,8 @@ export class BaseTransaction> {
      * depending on `whereFrom`, and pushes the element at the first/last element of the list
      * stored at `destination` depending on `whereTo`, see {@link ListDirection}.
      *
-     * See https://valkey.io/commands/lmove/ for details.
+     * @see {@link https://valkey.io/commands/lmove/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param source - The key to the source list.
      * @param destination - The key to the destination list.
@@ -926,8 +1063,6 @@ export class BaseTransaction> {
      * @param whereTo - The {@link ListDirection} to add the element to.
      *
      * Command Response - The popped element, or `null` if `source` does not exist.
-     *
-     * since Valkey version 6.2.0.
      */
     public lmove(
         source: string,
@@ -947,11 +1082,10 @@ export class BaseTransaction> {
      * of the list stored at `destination` depending on `whereTo`.
      * `BLMOVE` is the blocking variant of {@link lmove}.
      *
-     * @remarks
-     * 1. When in cluster mode, both `source` and `destination` must map to the same hash slot.
-     * 2. `BLMOVE` is a client blocking command, see https://github.com/aws/glide-for-redis/wiki/General-Concepts#blocking-commands for more details and best practices.
-     *
-     * See https://valkey.io/commands/blmove/ for details.
+     * @see {@link https://valkey.io/commands/blmove/|valkey.io} for details.
+     * @remarks When in cluster mode, both `source` and `destination` must map to the same hash slot.
+     * @remarks `BLMOVE` is a client blocking command, see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands|Valkey Glide Wiki} for more details and best practices.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param source - The key to the source list.
      * @param destination - The key to the destination list.
@@ -960,8 +1094,6 @@ export class BaseTransaction> {
      * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of `0` will block indefinitely.
      *
      * Command Response - The popped element, or `null` if `source` does not exist or if the operation timed-out.
-     *
-     * since Valkey version 6.2.0.
      */
     public blmove(
         source: string,
@@ -981,7 +1113,7 @@ export class BaseTransaction> {
      * Negative indices can be used to designate elements starting at the tail of
      * the list. Here, `-1` means the last element, `-2` means the penultimate and so forth.
      *
-     * See https://valkey.io/commands/lset/ for details.
+     * @see {@link https://valkey.io/commands/lset/|valkey.io} for details.
      *
      * @param key - The key of the list.
      * @param index - The index of the element in the list to be set.
@@ -997,7 +1129,7 @@ export class BaseTransaction> {
      * The offsets `start` and `end` are zero-based indexes, with 0 being the first element of the list, 1 being the next element and so on.
      * These offsets can also be negative numbers indicating offsets starting at the end of the list,
      * with -1 being the last element of the list, -2 being the penultimate, and so on.
-     * See https://valkey.io/commands/ltrim/ for details.
+     * @see {@link https://valkey.io/commands/ltrim/|valkey.io} for details.
      *
      * @param key - The key of the list.
      * @param start - The starting point of the range.
@@ -1031,7 +1163,7 @@ export class BaseTransaction> {
     /** Inserts all the specified values at the tail of the list stored at `key`.
      * `elements` are inserted one after the other to the tail of the list, from the leftmost element to the rightmost element.
      * If `key` does not exist, it is created as empty list before performing the push operations.
-     * See https://valkey.io/commands/rpush/ for details.
+     * @see {@link https://valkey.io/commands/rpush/|valkey.io} for details.
      *
      * @param key - The key of the list.
      * @param elements - The elements to insert at the tail of the list stored at `key`.
@@ -1046,7 +1178,7 @@ export class BaseTransaction> {
      * Inserts specified values at the tail of the `list`, only if `key` already
      * exists and holds a list.
      *
-     * See https://valkey.io/commands/rpushx/ for details.
+     * @see {@link https://valkey.io/commands/rpushx/|valkey.io} for details.
      *
      * @param key - The key of the list.
      * @param elements - The elements to insert at the tail of the list stored at `key`.
@@ -1059,7 +1191,7 @@ export class BaseTransaction> {
 
     /** Removes and returns the last elements of the list stored at `key`.
      * The command pops a single element from the end of the list.
-     * See https://valkey.io/commands/rpop/ for details.
+     * @see {@link https://valkey.io/commands/rpop/|valkey.io} for details.
      *
      * @param key - The key of the list.
      *
@@ -1071,7 +1203,7 @@ export class BaseTransaction> {
     }
 
     /** Removes and returns up to `count` elements from the list stored at `key`, depending on the list's length.
-     * See https://valkey.io/commands/rpop/ for details.
+     * @see {@link https://valkey.io/commands/rpop/|valkey.io} for details.
      *
      * @param key - The key of the list.
      * @param count - The count of the elements to pop from the list.
@@ -1085,7 +1217,7 @@ export class BaseTransaction> {
 
     /** Adds the specified members to the set stored at `key`. Specified members that are already a member of this set are ignored.
      * If `key` does not exist, a new set is created before adding `members`.
-     * See https://valkey.io/commands/sadd/ for details.
+     * @see {@link https://valkey.io/commands/sadd/|valkey.io} for details.
      *
      * @param key - The key to store the members to its set.
      * @param members - A list of members to add to the set stored at `key`.
@@ -1097,7 +1229,7 @@ export class BaseTransaction> {
     }
 
     /** Removes the specified members from the set stored at `key`. Specified members that are not a member of this set are ignored.
-     * See https://valkey.io/commands/srem/ for details.
+     * @see {@link https://valkey.io/commands/srem/|valkey.io} for details.
      *
      * @param key - The key to remove the members from its set.
      * @param members - A list of members to remove from the set stored at `key`.
@@ -1109,8 +1241,24 @@ export class BaseTransaction> {
         return this.addAndReturn(createSRem(key, members));
     }
 
+    /**
+     * Iterates incrementally over a set.
+     *
+     * @see {@link https://valkey.io/commands/sscan} for details.
+     *
+     * @param key - The key of the set.
+     * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of the search.
+     * @param options - The (Optional) {@link BaseScanOptions}.
+     *
+     * Command Response -  An array of the cursor and the subset of the set held by `key`. The first element is always the `cursor` and for the next iteration of results.
+     * The `cursor` will be `"0"` on the last iteration of the set. The second element is always an array of the subset of the set held in `key`.
+     */
+    public sscan(key: string, cursor: string, options?: BaseScanOptions): T {
+        return this.addAndReturn(createSScan(key, cursor, options));
+    }
+
     /** Returns all the members of the set value stored at `key`.
-     * See https://valkey.io/commands/smembers/ for details.
+     * @see {@link https://valkey.io/commands/smembers/|valkey.io} for details.
      *
      * @param key - The key to return its members.
      *
@@ -1123,7 +1271,7 @@ export class BaseTransaction> {
 
     /** Moves `member` from the set at `source` to the set at `destination`, removing it from the source set.
      * Creates a new destination set if needed. The operation is atomic.
-     * See https://valkey.io/commands/smove for more details.
+     * @see {@link https://valkey.io/commands/smove/|valkey.io} for more details.
      *
      * @param source - The key of the set to remove the element from.
      * @param destination - The key of the set to add the element to.
@@ -1136,7 +1284,7 @@ export class BaseTransaction> {
     }
 
     /** Returns the set cardinality (number of elements) of the set stored at `key`.
-     * See https://valkey.io/commands/scard/ for details.
+     * @see {@link https://valkey.io/commands/scard/|valkey.io} for details.
      *
      * @param key - The key to return the number of its members.
      *
@@ -1148,7 +1296,7 @@ export class BaseTransaction> {
 
     /** Gets the intersection of all the given sets.
      * When in cluster mode, all `keys` must map to the same hash slot.
-     * See https://valkey.io/docs/latest/commands/sinter/ for more details.
+     * @see {@link https://valkey.io/commands/sinter/|valkey.io} for details.
      *
      * @param keys - The `keys` of the sets to get the intersection.
      *
@@ -1162,13 +1310,12 @@ export class BaseTransaction> {
     /**
      * Gets the cardinality of the intersection of all the given sets.
      *
-     * See https://valkey.io/commands/sintercard/ for more details.
+     * @see {@link https://valkey.io/commands/sintercard/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param keys - The keys of the sets.
      *
      * Command Response - The cardinality of the intersection result. If one or more sets do not exist, `0` is returned.
-     *
-     * since Valkey version 7.0.0.
      */
     public sintercard(keys: string[], limit?: number): T {
         return this.addAndReturn(createSInterCard(keys, limit));
@@ -1177,7 +1324,7 @@ export class BaseTransaction> {
     /**
      * Stores the members of the intersection of all given sets specified by `keys` into a new set at `destination`.
      *
-     * See https://valkey.io/commands/sinterstore/ for more details.
+     * @see {@link https://valkey.io/commands/sinterstore/|valkey.io} for details.
      *
      * @param destination - The key of the destination set.
      * @param keys - The keys from which to retrieve the set members.
@@ -1191,7 +1338,7 @@ export class BaseTransaction> {
     /**
      * Computes the difference between the first set and all the successive sets in `keys`.
      *
-     * See https://valkey.io/commands/sdiff/ for more details.
+     * @see {@link https://valkey.io/commands/sdiff/|valkey.io} for details.
      *
      * @param keys - The keys of the sets to diff.
      *
@@ -1205,7 +1352,7 @@ export class BaseTransaction> {
     /**
      * Stores the difference between the first set and all the successive sets in `keys` into a new set at `destination`.
      *
-     * See https://valkey.io/commands/sdiffstore/ for more details.
+     * @see {@link https://valkey.io/commands/sdiffstore/|valkey.io} for details.
      *
      * @param destination - The key of the destination set.
      * @param keys - The keys of the sets to diff.
@@ -1219,7 +1366,7 @@ export class BaseTransaction> {
     /**
      * Gets the union of all the given sets.
      *
-     * See https://valkey.io/commands/sunion/ for more details.
+     * @see {@link https://valkey.io/commands/sunion/|valkey.io} for details.
      *
      * @param keys - The keys of the sets.
      *
@@ -1234,7 +1381,7 @@ export class BaseTransaction> {
      * Stores the members of the union of all given sets specified by `keys` into a new set
      * at `destination`.
      *
-     * See https://valkey.io/commands/sunionstore/ for details.
+     * @see {@link https://valkey.io/commands/sunionstore/|valkey.io} for details.
      *
      * @param destination - The key of the destination set.
      * @param keys - The keys from which to retrieve the set members.
@@ -1246,7 +1393,7 @@ export class BaseTransaction> {
     }
 
     /** Returns if `member` is a member of the set stored at `key`.
-     * See https://valkey.io/commands/sismember/ for more details.
+     * @see {@link https://valkey.io/commands/sismember/|valkey.io} for details.
      *
      * @param key - The key of the set.
      * @param member - The member to check for existence in the set.
@@ -1261,21 +1408,20 @@ export class BaseTransaction> {
     /**
      * Checks whether each member is contained in the members of the set stored at `key`.
      *
-     * See https://valkey.io/commands/smismember/ for more details.
+     * @see {@link https://valkey.io/commands/smismember/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param key - The key of the set to check.
      * @param members - A list of members to check for existence in the set.
      *
      * Command Response - An `array` of `boolean` values, each indicating if the respective member exists in the set.
-     *
-     * since Valkey version 6.2.0.
      */
     public smismember(key: string, members: string[]): T {
         return this.addAndReturn(createSMIsMember(key, members));
     }
 
     /** Removes and returns one random member from the set value store at `key`.
-     * See https://valkey.io/commands/spop/ for details.
+     * @see {@link https://valkey.io/commands/spop/|valkey.io} for details.
      * To pop multiple members, see `spopCount`.
      *
      * @param key - The key of the set.
@@ -1288,7 +1434,7 @@ export class BaseTransaction> {
     }
 
     /** Removes and returns up to `count` random members from the set value store at `key`, depending on the set's length.
-     * See https://valkey.io/commands/spop/ for details.
+     * @see {@link https://valkey.io/commands/spop/|valkey.io} for details.
      *
      * @param key - The key of the set.
      * @param count - The count of the elements to pop from the set.
@@ -1300,8 +1446,33 @@ export class BaseTransaction> {
         return this.addAndReturn(createSPop(key, count), true);
     }
 
+    /** Returns a random element from the set value stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/srandmember/|valkey.io} for more details.
+     *
+     * @param key - The key from which to retrieve the set member.
+     * Command Response - A random element from the set, or null if `key` does not exist.
+     */
+    public srandmember(key: string): T {
+        return this.addAndReturn(createSRandMember(key));
+    }
+
+    /** Returns one or more random elements from the set value stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/srandmember/|valkey.io} for more details.
+     *
+     * @param key - The key of the sorted set.
+     * @param count - The number of members to return.
+     *                If `count` is positive, returns unique members.
+     *                If `count` is negative, allows for duplicates members.
+     * Command Response - A list of members from the set. If the set does not exist or is empty, an empty list will be returned.
+     */
+    public srandmemberCount(key: string, count: number): T {
+        return this.addAndReturn(createSRandMember(key, count));
+    }
+
     /** Returns the number of keys in `keys` that exist in the database.
-     * See https://valkey.io/commands/exists/ for details.
+     * @see {@link https://valkey.io/commands/exists/|valkey.io} for details.
      *
      * @param keys - The keys list to check.
      *
@@ -1315,7 +1486,7 @@ export class BaseTransaction> {
     /** Removes the specified keys. A key is ignored if it does not exist.
      * This command, similar to DEL, removes specified keys and ignores non-existent ones.
      * However, this command does not block the server, while [DEL](https://valkey.io/commands/del) does.
-     * See https://valkey.io/commands/unlink/ for details.
+     * @see {@link https://valkey.io/commands/unlink/|valkey.io} for details.
      *
      * @param keys - The keys we wanted to unlink.
      *
@@ -1329,7 +1500,7 @@ export class BaseTransaction> {
      * If `key` already has an existing expire set, the time to live is updated to the new value.
      * If `seconds` is non-positive number, the key will be deleted rather than expired.
      * The timeout will only be cleared by commands that delete or overwrite the contents of `key`.
-     * See https://valkey.io/commands/expire/ for details.
+     * @see {@link https://valkey.io/commands/expire/|valkey.io} for details.
      *
      * @param key - The key to set timeout on it.
      * @param seconds - The timeout in seconds.
@@ -1346,7 +1517,7 @@ export class BaseTransaction> {
      * A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be deleted.
      * If `key` already has an existing expire set, the time to live is updated to the new value.
      * The timeout will only be cleared by commands that delete or overwrite the contents of `key`.
-     * See https://valkey.io/commands/expireat/ for details.
+     * @see {@link https://valkey.io/commands/expireat/|valkey.io} for details.
      *
      * @param key - The key to set timeout on it.
      * @param unixSeconds - The timeout in an absolute Unix timestamp.
@@ -1367,13 +1538,12 @@ export class BaseTransaction> {
      * Returns the absolute Unix timestamp (since January 1, 1970) at which the given `key` will expire, in seconds.
      * To get the expiration with millisecond precision, use {@link pexpiretime}.
      *
-     * See https://valkey.io/commands/expiretime/ for details.
+     * @see {@link https://valkey.io/commands/expiretime/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key - The `key` to determine the expiration value of.
      *
      * Command Response - The expiration Unix timestamp in seconds, `-2` if `key` does not exist or `-1` if `key` exists but has no associated expire.
-     *
-     * since Valkey version 7.0.0.
      */
     public expireTime(key: string): T {
         return this.addAndReturn(createExpireTime(key));
@@ -1383,7 +1553,7 @@ export class BaseTransaction> {
      * If `key` already has an existing expire set, the time to live is updated to the new value.
      * If `milliseconds` is non-positive number, the key will be deleted rather than expired.
      * The timeout will only be cleared by commands that delete or overwrite the contents of `key`.
-     * See https://valkey.io/commands/pexpire/ for details.
+     * @see {@link https://valkey.io/commands/pexpire/|valkey.io} for details.
      *
      * @param key - The key to set timeout on it.
      * @param milliseconds - The timeout in milliseconds.
@@ -1404,7 +1574,7 @@ export class BaseTransaction> {
      * A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be deleted.
      * If `key` already has an existing expire set, the time to live is updated to the new value.
      * The timeout will only be cleared by commands that delete or overwrite the contents of `key`.
-     * See https://valkey.io/commands/pexpireat/ for details.
+     * @see {@link https://valkey.io/commands/pexpireat/|valkey.io} for details.
      *
      * @param key - The key to set timeout on it.
      * @param unixMilliseconds - The timeout in an absolute Unix timestamp.
@@ -1426,20 +1596,19 @@ export class BaseTransaction> {
     /**
      * Returns the absolute Unix timestamp (since January 1, 1970) at which the given `key` will expire, in milliseconds.
      *
-     * See https://valkey.io/commands/pexpiretime/ for details.
+     * @see {@link https://valkey.io/commands/pexpiretime/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key - The `key` to determine the expiration value of.
      *
      * Command Response - The expiration Unix timestamp in seconds, `-2` if `key` does not exist or `-1` if `key` exists but has no associated expire.
-     *
-     * since Valkey version 7.0.0.
      */
     public pexpireTime(key: string): T {
         return this.addAndReturn(createPExpireTime(key));
     }
 
     /** Returns the remaining time to live of `key` that has a timeout.
-     * See https://valkey.io/commands/ttl/ for details.
+     * @see {@link https://valkey.io/commands/ttl/|valkey.io} for details.
      *
      * @param key - The key to return its timeout.
      *
@@ -1451,7 +1620,7 @@ export class BaseTransaction> {
 
     /** Adds members with their scores to the sorted set stored at `key`.
      * If a member is already a part of the sorted set, its score is updated.
-     * See https://valkey.io/commands/zadd/ for more details.
+     * @see {@link https://valkey.io/commands/zadd/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param membersScoresMap - A mapping of members to their corresponding scores.
@@ -1471,7 +1640,7 @@ export class BaseTransaction> {
     /** Increments the score of member in the sorted set stored at `key` by `increment`.
      * If `member` does not exist in the sorted set, it is added with `increment` as its score (as if its previous score was 0.0).
      * If `key` does not exist, a new sorted set with the specified member as its sole member is created.
-     * See https://valkey.io/commands/zadd/ for more details.
+     * @see {@link https://valkey.io/commands/zadd/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param member - A member in the sorted set to increment.
@@ -1494,7 +1663,7 @@ export class BaseTransaction> {
 
     /** Removes the specified members from the sorted set stored at `key`.
      * Specified members that are not a member of this set are ignored.
-     * See https://valkey.io/commands/zrem/ for more details.
+     * @see {@link https://valkey.io/commands/zrem/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param members - A list of members to remove from the sorted set.
@@ -1507,7 +1676,7 @@ export class BaseTransaction> {
     }
 
     /** Returns the cardinality (number of elements) of the sorted set stored at `key`.
-     * See https://valkey.io/commands/zcard/ for more details.
+     * @see {@link https://valkey.io/commands/zcard/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      *
@@ -1521,15 +1690,14 @@ export class BaseTransaction> {
     /**
      * Returns the cardinality of the intersection of the sorted sets specified by `keys`.
      *
-     * See https://valkey.io/commands/zintercard/ for more details.
+     * @see {@link https://valkey.io/commands/zintercard/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param keys - The keys of the sorted sets to intersect.
      * @param limit - An optional argument that can be used to specify a maximum number for the
      * intersection cardinality. If limit is not supplied, or if it is set to `0`, there will be no limit.
      *
      * Command Response - The cardinality of the intersection of the given sorted sets.
-     *
-     * since - Redis version 7.0.0.
      */
     public zintercard(keys: string[], limit?: number): T {
         return this.addAndReturn(createZInterCard(keys, limit));
@@ -1539,14 +1707,13 @@ export class BaseTransaction> {
      * Returns the difference between the first sorted set and all the successive sorted sets.
      * To get the elements with their scores, see {@link zdiffWithScores}.
      *
-     * See https://valkey.io/commands/zdiff/ for more details.
+     * @see {@link https://valkey.io/commands/zdiff/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param keys - The keys of the sorted sets.
      *
      * Command Response - An `array` of elements representing the difference between the sorted sets.
      * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`.
-     *
-     * since Valkey version 6.2.0.
      */
     public zdiff(keys: string[]): T {
         return this.addAndReturn(createZDiff(keys));
@@ -1556,14 +1723,13 @@ export class BaseTransaction> {
      * Returns the difference between the first sorted set and all the successive sorted sets, with the associated
      * scores.
      *
-     * See https://valkey.io/commands/zdiff/ for more details.
+     * @see {@link https://valkey.io/commands/zdiff/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param keys - The keys of the sorted sets.
      *
      * Command Response - A map of elements and their scores representing the difference between the sorted sets.
      * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`.
-     *
-     * since Valkey version 6.2.0.
      */
     public zdiffWithScores(keys: string[]): T {
         return this.addAndReturn(createZDiffWithScores(keys));
@@ -1574,21 +1740,20 @@ export class BaseTransaction> {
      * the difference as a sorted set to `destination`, overwriting it if it already exists. Non-existent keys are
      * treated as empty sets.
      *
-     * See https://valkey.io/commands/zdiffstore/ for more details.
+     * @see {@link https://valkey.io/commands/zdiffstore/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param destination - The key for the resulting sorted set.
      * @param keys - The keys of the sorted sets to compare.
      *
      * Command Response - The number of members in the resulting sorted set stored at `destination`.
-     *
-     * since Valkey version 6.2.0.
      */
     public zdiffstore(destination: string, keys: string[]): T {
         return this.addAndReturn(createZDiffStore(destination, keys));
     }
 
     /** Returns the score of `member` in the sorted set stored at `key`.
-     * See https://valkey.io/commands/zscore/ for more details.
+     * @see {@link https://valkey.io/commands/zscore/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param member - The member whose score is to be retrieved.
@@ -1604,22 +1769,21 @@ export class BaseTransaction> {
     /**
      * Returns the scores associated with the specified `members` in the sorted set stored at `key`.
      *
-     * See https://valkey.io/commands/zmscore/ for more details.
+     * @see {@link https://valkey.io/commands/zmscore/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param key - The key of the sorted set.
      * @param members - A list of members in the sorted set.
      *
      * Command Response - An `array` of scores corresponding to `members`.
      * If a member does not exist in the sorted set, the corresponding value in the list will be `null`.
-     *
-     * since Valkey version 6.2.0.
      */
     public zmscore(key: string, members: string[]): T {
         return this.addAndReturn(createZMScore(key, members));
     }
 
     /** Returns the number of members in the sorted set stored at `key` with scores between `minScore` and `maxScore`.
-     * See https://valkey.io/commands/zcount/ for more details.
+     * @see {@link https://valkey.io/commands/zcount/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param minScore - The minimum score to count from. Can be positive/negative infinity, or specific score and inclusivity.
@@ -1631,8 +1795,8 @@ export class BaseTransaction> {
      */
     public zcount(
         key: string,
-        minScore: ScoreBoundary,
-        maxScore: ScoreBoundary,
+        minScore: Boundary,
+        maxScore: Boundary,
     ): T {
         return this.addAndReturn(createZCount(key, minScore, maxScore));
     }
@@ -1640,15 +1804,15 @@ export class BaseTransaction> {
     /** Returns the specified range of elements in the sorted set stored at `key`.
      * ZRANGE can perform different types of range queries: by index (rank), by the score, or by lexicographical order.
      *
-     * See https://valkey.io/commands/zrange/ for more details.
+     * @see {@link https://valkey.io/commands/zrange/|valkey.io} for details.
      * To get the elements with their scores, see `zrangeWithScores`.
      *
      * @param key - The key of the sorted set.
      * @param rangeQuery - The range query object representing the type of range query to perform.
-     * For range queries by index (rank), use RangeByIndex.
-     * For range queries by lexicographical order, use RangeByLex.
-     * For range queries by score, use RangeByScore.
-     * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score.
+     * - For range queries by index (rank), use {@link RangeByIndex}.
+     * - For range queries by lexicographical order, use {@link RangeByLex}.
+     * - For range queries by score, use {@link RangeByScore}.
+     * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score.
      *
      * Command Response - A list of elements within the specified range.
      * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty array.
@@ -1663,14 +1827,14 @@ export class BaseTransaction> {
 
     /** Returns the specified range of elements with their scores in the sorted set stored at `key`.
      * Similar to ZRANGE but with a WITHSCORE flag.
-     * See https://valkey.io/commands/zrange/ for more details.
+     * @see {@link https://valkey.io/commands/zrange/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param rangeQuery - The range query object representing the type of range query to perform.
-     * For range queries by index (rank), use RangeByIndex.
-     * For range queries by lexicographical order, use RangeByLex.
-     * For range queries by score, use RangeByScore.
-     * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score.
+     * - For range queries by index (rank), use {@link RangeByIndex}.
+     * - For range queries by lexicographical order, use {@link RangeByLex}.
+     * - For range queries by score, use {@link RangeByScore}.
+     * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score.
      *
      * Command Response - A map of elements and their scores within the specified range.
      * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty map.
@@ -1685,13 +1849,42 @@ export class BaseTransaction> {
         );
     }
 
+    /**
+     * Stores a specified range of elements from the sorted set at `source`, into a new
+     * sorted set at `destination`. If `destination` doesn't exist, a new sorted
+     * set is created; if it exists, it's overwritten.
+     *
+     * @see {@link https://valkey.io/commands/zrangestore/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param destination - The key for the destination sorted set.
+     * @param source - The key of the source sorted set.
+     * @param rangeQuery - The range query object representing the type of range query to perform.
+     * - For range queries by index (rank), use {@link RangeByIndex}.
+     * - For range queries by lexicographical order, use {@link RangeByLex}.
+     * - For range queries by score, use {@link RangeByScore}.
+     * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score.
+     *
+     * Command Response - The number of elements in the resulting sorted set.
+     */
+    public zrangeStore(
+        destination: string,
+        source: string,
+        rangeQuery: RangeByScore | RangeByLex | RangeByIndex,
+        reverse: boolean = false,
+    ): T {
+        return this.addAndReturn(
+            createZRangeStore(destination, source, rangeQuery, reverse),
+        );
+    }
+
     /**
      * Computes the intersection of sorted sets given by the specified `keys` and stores the result in `destination`.
      * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created.
      *
      * When in cluster mode, `destination` and all keys in `keys` must map to the same hash slot.
      *
-     * See https://valkey.io/commands/zinterstore/ for more details.
+     * @see {@link https://valkey.io/commands/zinterstore/|valkey.io} for details.
      *
      * @param destination - The key of the destination sorted set.
      * @param keys - The keys of the sorted sets with possible formats:
@@ -1713,7 +1906,7 @@ export class BaseTransaction> {
     /**
      * Returns a random member from the sorted set stored at `key`.
      *
-     * See https://valkey.io/commands/zrandmember/ for more details.
+     * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for details.
      *
      * @param keys - The key of the sorted set.
      * Command Response - A string representing a random member from the sorted set.
@@ -1726,7 +1919,7 @@ export class BaseTransaction> {
     /**
      * Returns random members from the sorted set stored at `key`.
      *
-     * See https://valkey.io/commands/zrandmember/ for more details.
+     * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for details.
      *
      * @param keys - The key of the sorted set.
      * @param count - The number of members to return.
@@ -1742,7 +1935,7 @@ export class BaseTransaction> {
     /**
      * Returns random members with scores from the sorted set stored at `key`.
      *
-     * See https://valkey.io/commands/zrandmember/ for more details.
+     * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for details.
      *
      * @param keys - The key of the sorted set.
      * @param count - The number of members to return.
@@ -1757,7 +1950,7 @@ export class BaseTransaction> {
     }
 
     /** Returns the string representation of the type of the value stored at `key`.
-     * See https://valkey.io/commands/type/ for more details.
+     * @see {@link https://valkey.io/commands/type/|valkey.io} for details.
      *
      * @param key - The key to check its data type.
      *
@@ -1768,7 +1961,7 @@ export class BaseTransaction> {
     }
 
     /** Returns the length of the string value stored at `key`.
-     * See https://valkey.io/commands/strlen/ for more details.
+     * @see {@link https://valkey.io/commands/strlen/|valkey.io} for details.
      *
      * @param key - The `key` to check its length.
      *
@@ -1782,7 +1975,7 @@ export class BaseTransaction> {
     /** Removes and returns the members with the lowest scores from the sorted set stored at `key`.
      * If `count` is provided, up to `count` members with the lowest scores are removed and returned.
      * Otherwise, only one member with the lowest score is removed and returned.
-     * See https://valkey.io/commands/zpopmin for more details.
+     * @see {@link https://valkey.io/commands/zpopmin/|valkey.io} for more details.
      *
      * @param key - The key of the sorted set.
      * @param count - Specifies the quantity of members to pop. If not specified, pops one member.
@@ -1795,10 +1988,29 @@ export class BaseTransaction> {
         return this.addAndReturn(createZPopMin(key, count));
     }
 
+    /**
+     * Blocks the connection until it removes and returns a member with the lowest score from the
+     * first non-empty sorted set, with the given `key` being checked in the order they
+     * are provided.
+     * `BZPOPMIN` is the blocking variant of {@link zpopmin}.
+     *
+     * @see {@link https://valkey.io/commands/bzpopmin/|valkey.io} for details.
+     *
+     * @param keys - The keys of the sorted sets.
+     * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of
+     *     `0` will block indefinitely. Since Valkey version 6.0.0: timeout is interpreted as a double instead of an integer.
+     *
+     * Command Response - An `array` containing the key where the member was popped out, the member, itself, and the member score.
+     *     If no member could be popped and the `timeout` expired, returns `null`.
+     */
+    public bzpopmin(keys: string[], timeout: number): T {
+        return this.addAndReturn(createBZPopMin(keys, timeout));
+    }
+
     /** Removes and returns the members with the highest scores from the sorted set stored at `key`.
      * If `count` is provided, up to `count` members with the highest scores are removed and returned.
      * Otherwise, only one member with the highest score is removed and returned.
-     * See https://valkey.io/commands/zpopmax for more details.
+     * @see {@link https://valkey.io/commands/zpopmax/|valkey.io} for more details.
      *
      * @param key - The key of the sorted set.
      * @param count - Specifies the quantity of members to pop. If not specified, pops one member.
@@ -1811,8 +2023,27 @@ export class BaseTransaction> {
         return this.addAndReturn(createZPopMax(key, count));
     }
 
+    /**
+     * Blocks the connection until it removes and returns a member with the highest score from the
+     * first non-empty sorted set, with the given `key` being checked in the order they
+     * are provided.
+     * `BZPOPMAX` is the blocking variant of {@link zpopmax}.
+     *
+     * @see {@link https://valkey.io/commands/bzpopmax/|valkey.io} for details.
+     *
+     * @param keys - The keys of the sorted sets.
+     * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of
+     *     `0` will block indefinitely. Since 6.0.0: timeout is interpreted as a double instead of an integer.
+     *
+     * Command Response - An `array` containing the key where the member was popped out, the member, itself, and the member score.
+     *     If no member could be popped and the `timeout` expired, returns `null`.
+     */
+    public bzpopmax(keys: string[], timeout: number): T {
+        return this.addAndReturn(createBZPopMax(keys, timeout));
+    }
+
     /** Echoes the provided `message` back.
-     * See https://valkey.io/commands/echo for more details.
+     * @see {@link https://valkey.io/commands/echo/|valkey.io} for more details.
      *
      * @param message - The message to be echoed back.
      *
@@ -1823,7 +2054,7 @@ export class BaseTransaction> {
     }
 
     /** Returns the remaining time to live of `key` that has a timeout, in milliseconds.
-     * See https://valkey.io/commands/pttl for more details.
+     * @see {@link https://valkey.io/commands/pttl/|valkey.io} for more details.
      *
      * @param key - The key to return its timeout.
      *
@@ -1836,7 +2067,7 @@ export class BaseTransaction> {
     /** Removes all elements in the sorted set stored at `key` with rank between `start` and `end`.
      * Both `start` and `end` are zero-based indexes with 0 being the element with the lowest score.
      * These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score.
-     * See https://valkey.io/commands/zremrangebyrank/ for more details.
+     * @see {@link https://valkey.io/commands/zremrangebyrank/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param start - The starting point of the range.
@@ -1854,7 +2085,7 @@ export class BaseTransaction> {
     /**
      * Removes all elements in the sorted set stored at `key` with lexicographical order between `minLex` and `maxLex`.
      *
-     * See https://valkey.io/commands/zremrangebylex/ for more details.
+     * @see {@link https://valkey.io/commands/zremrangebylex/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param minLex - The minimum lex to count from. Can be positive/negative infinity, or a specific lex and inclusivity.
@@ -1866,14 +2097,14 @@ export class BaseTransaction> {
      */
     public zremRangeByLex(
         key: string,
-        minLex: ScoreBoundary,
-        maxLex: ScoreBoundary,
+        minLex: Boundary,
+        maxLex: Boundary,
     ): T {
         return this.addAndReturn(createZRemRangeByLex(key, minLex, maxLex));
     }
 
     /** Removes all elements in the sorted set stored at `key` with a score between `minScore` and `maxScore`.
-     * See https://valkey.io/commands/zremrangebyscore/ for more details.
+     * @see {@link https://valkey.io/commands/zremrangebyscore/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param minScore - The minimum score to remove from. Can be positive/negative infinity, or specific score and inclusivity.
@@ -1885,8 +2116,8 @@ export class BaseTransaction> {
      */
     public zremRangeByScore(
         key: string,
-        minScore: ScoreBoundary,
-        maxScore: ScoreBoundary,
+        minScore: Boundary,
+        maxScore: Boundary,
     ): T {
         return this.addAndReturn(
             createZRemRangeByScore(key, minScore, maxScore),
@@ -1896,7 +2127,7 @@ export class BaseTransaction> {
     /**
      * Returns the number of members in the sorted set stored at 'key' with scores between 'minLex' and 'maxLex'.
      *
-     * See https://valkey.io/commands/zlexcount/ for more details.
+     * @see {@link https://valkey.io/commands/zlexcount/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param minLex - The minimum lex to count from. Can be positive/negative infinity, or a specific lex and inclusivity.
@@ -1908,14 +2139,14 @@ export class BaseTransaction> {
      */
     public zlexcount(
         key: string,
-        minLex: ScoreBoundary,
-        maxLex: ScoreBoundary,
+        minLex: Boundary,
+        maxLex: Boundary,
     ): T {
         return this.addAndReturn(createZLexCount(key, minLex, maxLex));
     }
 
     /** Returns the rank of `member` in the sorted set stored at `key`, with scores ordered from low to high.
-     * See https://valkey.io/commands/zrank for more details.
+     * @see {@link https://valkey.io/commands/zrank/|valkey.io} for more details.
      * To get the rank of `member` with its score, see `zrankWithScore`.
      *
      * @param key - The key of the sorted set.
@@ -1929,15 +2160,15 @@ export class BaseTransaction> {
     }
 
     /** Returns the rank of `member` in the sorted set stored at `key` with its score, where scores are ordered from the lowest to highest.
-     * See https://valkey.io/commands/zrank for more details.
+     *
+     * @see {@link https://valkey.io/commands/zrank/|valkey.io} for more details.
+     * @remarks Since Valkey version 7.2.0.
      *
      * @param key - The key of the sorted set.
      * @param member - The member whose rank is to be retrieved.
      *
      * Command Response - A list containing the rank and score of `member` in the sorted set.
      * If `key` doesn't exist, or if `member` is not present in the set, null will be returned.
-     *
-     * since - Redis version 7.2.0.
      */
     public zrankWithScore(key: string, member: string): T {
         return this.addAndReturn(createZRank(key, member, true));
@@ -1948,7 +2179,7 @@ export class BaseTransaction> {
      * scores are ordered from the highest to lowest, starting from 0.
      * To get the rank of `member` with its score, see {@link zrevrankWithScore}.
      *
-     * See https://valkey.io/commands/zrevrank/ for more details.
+     * @see {@link https://valkey.io/commands/zrevrank/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param member - The member whose rank is to be retrieved.
@@ -1964,7 +2195,8 @@ export class BaseTransaction> {
      * Returns the rank of `member` in the sorted set stored at `key` with its
      * score, where scores are ordered from the highest to lowest, starting from 0.
      *
-     * See https://valkey.io/commands/zrevrank/ for more details.
+     * @see {@link https://valkey.io/commands/zrevrank/|valkey.io} for details.
+     * @remarks Since Valkey version 7.2.0.
      *
      * @param key - The key of the sorted set.
      * @param member - The member whose rank is to be retrieved.
@@ -1972,8 +2204,6 @@ export class BaseTransaction> {
      * Command Response -  A list containing the rank and score of `member` in the sorted set, where ranks
      *     are ordered from high to low based on scores.
      *     If `key` doesn't exist, or if `member` is not present in the set, `null` will be returned.
-     *
-     * since - Valkey version 7.2.0.
      */
     public zrevrankWithScore(key: string, member: string): T {
         return this.addAndReturn(createZRevRankWithScore(key, member));
@@ -1981,7 +2211,7 @@ export class BaseTransaction> {
 
     /** Remove the existing timeout on `key`, turning the key from volatile (a key with an expire set) to
      * persistent (a key that will never expire as no timeout is associated).
-     * See https://valkey.io/commands/persist/ for more details.
+     * @see {@link https://valkey.io/commands/persist/|valkey.io} for details.
      *
      * @param key - The key to remove the existing timeout on.
      *
@@ -1994,12 +2224,11 @@ export class BaseTransaction> {
     /** Executes a single command, without checking inputs. Every part of the command, including subcommands,
      *  should be added as a separate value in args.
      *
-     * See the [Glide for Redis Wiki](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command)
-     * for details on the restrictions and limitations of the custom command API.
+     * @see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command|Valkey Glide Wiki} for details on the restrictions and limitations of the custom command API.
      *
      * Command Response - A response from Redis with an `Object`.
      */
-    public customCommand(args: string[]): T {
+    public customCommand(args: GlideString[]): T {
         return this.addAndReturn(createCustomCommand(args));
     }
 
@@ -2007,7 +2236,7 @@ export class BaseTransaction> {
      * The index is zero-based, so 0 means the first element, 1 the second element and so on.
      * Negative indices can be used to designate elements starting at the tail of the list.
      * Here, -1 means the last element, -2 means the penultimate and so forth.
-     * See https://valkey.io/commands/lindex/ for more details.
+     * @see {@link https://valkey.io/commands/lindex/|valkey.io} for details.
      *
      * @param key - The `key` of the list.
      * @param index - The `index` of the element in the list to retrieve.
@@ -2021,7 +2250,7 @@ export class BaseTransaction> {
     /**
      * Inserts `element` in the list at `key` either before or after the `pivot`.
      *
-     * See https://valkey.io/commands/linsert/ for more details.
+     * @see {@link https://valkey.io/commands/linsert/|valkey.io} for details.
      *
      * @param key - The key of the list.
      * @param position - The relative position to insert into - either `InsertPosition.Before` or
@@ -2044,7 +2273,7 @@ export class BaseTransaction> {
 
     /**
      * Adds an entry to the specified stream stored at `key`. If the `key` doesn't exist, the stream is created.
-     * See https://valkey.io/commands/xadd/ for more details.
+     * @see {@link https://valkey.io/commands/xadd/|valkey.io} for details.
      *
      * @param key - The key of the stream.
      * @param values - field-value pairs to be added to the entry.
@@ -2063,7 +2292,7 @@ export class BaseTransaction> {
     /**
      * Removes the specified entries by id from a stream, and returns the number of entries deleted.
      *
-     * See https://valkey.io/commands/xdel for more details.
+     * @see {@link https://valkey.io/commands/xdel/|valkey.io} for more details.
      *
      * @param key - The key of the stream.
      * @param ids - An array of entry ids.
@@ -2077,7 +2306,7 @@ export class BaseTransaction> {
 
     /**
      * Trims the stream stored at `key` by evicting older entries.
-     * See https://valkey.io/commands/xtrim/ for more details.
+     * @see {@link https://valkey.io/commands/xtrim/|valkey.io} for details.
      *
      * @param key - the key of the stream
      * @param options - options detailing how to trim the stream.
@@ -2088,8 +2317,37 @@ export class BaseTransaction> {
         return this.addAndReturn(createXTrim(key, options));
     }
 
+    /**
+     * Returns information about the stream stored at `key`.
+     *
+     * @param key - The key of the stream.
+     * @param fullOptions - If `true`, returns verbose information with a limit of the first 10 PEL entries.
+     * If `number` is specified, returns verbose information limiting the returned PEL entries.
+     * If `0` is specified, returns verbose information with no limit.
+     *
+     * Command Response - A {@link ReturnTypeXinfoStream} of detailed stream information for the given `key`.
+     *     See example of {@link BaseClient.xinfoStream} for more details.
+     */
+    public xinfoStream(key: string, fullOptions?: boolean | number): T {
+        return this.addAndReturn(createXInfoStream(key, fullOptions ?? false));
+    }
+
+    /**
+     * Returns the list of all consumer groups and their attributes for the stream stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/xinfo-groups/|valkey.io} for details.
+     *
+     * @param key - The key of the stream.
+     *
+     * Command Response -  An `Array` of `Records`, where each mapping represents the
+     *     attributes of a consumer group for the stream at `key`.
+     */
+    public xinfoGroups(key: string): T {
+        return this.addAndReturn(createXInfoGroups(key));
+    }
+
     /** Returns the server time.
-     * See https://valkey.io/commands/time/ for details.
+     * @see {@link https://valkey.io/commands/time/|valkey.io} for details.
      *
      * Command Response - The current server time as a two items `array`:
      * A Unix timestamp and the amount of microseconds already elapsed in the current second.
@@ -2099,9 +2357,37 @@ export class BaseTransaction> {
         return this.addAndReturn(createTime());
     }
 
+    /**
+     * Returns stream entries matching a given range of entry IDs.
+     *
+     * @see {@link https://valkey.io/commands/xrange/|valkey.io} for more details.
+     *
+     * @param key - The key of the stream.
+     * @param start - The starting stream entry ID bound for the range.
+     *     - Use `value` to specify a stream entry ID.
+     *     - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0.
+     *     - Use `InfBoundary.NegativeInfinity` to start with the minimum available ID.
+     * @param end - The ending stream ID bound for the range.
+     *     - Use `value` to specify a stream entry ID.
+     *     - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0.
+     *     - Use `InfBoundary.PositiveInfinity` to end with the maximum available ID.
+     * @param count - An optional argument specifying the maximum count of stream entries to return.
+     *     If `count` is not provided, all stream entries in the range will be returned.
+     *
+     * Command Response - A map of stream entry ids, to an array of entries, or `null` if `count` is negative.
+     */
+    public xrange(
+        key: string,
+        start: Boundary,
+        end: Boundary,
+        count?: number,
+    ): T {
+        return this.addAndReturn(createXRange(key, start, end, count));
+    }
+
     /**
      * Reads entries from the given streams.
-     * See https://valkey.io/commands/xread/ for more details.
+     * @see {@link https://valkey.io/commands/xread/|valkey.io} for details.
      *
      * @param keys_and_ids - pairs of keys and entry ids to read from. A pair is composed of a stream's key and the id of the entry after which the stream will be read.
      * @param options - options detailing how to read the stream.
@@ -2118,7 +2404,7 @@ export class BaseTransaction> {
     /**
      * Returns the number of entries in the stream stored at `key`.
      *
-     * See https://valkey.io/commands/xlen/ for more details.
+     * @see {@link https://valkey.io/commands/xlen/|valkey.io} for details.
      *
      * @param key - The key of the stream.
      *
@@ -2128,11 +2414,198 @@ export class BaseTransaction> {
         return this.addAndReturn(createXLen(key));
     }
 
+    /**
+     * Returns stream message summary information for pending messages matching a given range of IDs.
+     *
+     * @see {@link https://valkey.io/commands/xpending/|valkey.io} for details.
+     * Returns the list of all consumers and their attributes for the given consumer group of the
+     * stream stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/xinfo-consumers/|valkey.io} for details.
+     *
+     * @param key - The key of the stream.
+     * @param group - The consumer group name.
+     *
+     * Command Response - An `array` that includes the summary of the pending messages.
+     * See example of {@link BaseClient.xpending|xpending} for more details.
+     */
+    public xpending(key: string, group: string): T {
+        return this.addAndReturn(createXPending(key, group));
+    }
+
+    /**
+     * Returns stream message summary information for pending messages matching a given range of IDs.
+     *
+     * @see {@link https://valkey.io/commands/xpending/|valkey.io} for details.
+     *
+     * @param key - The key of the stream.
+     * @param group - The consumer group name.
+     * @param options - Additional options to filter entries, see {@link StreamPendingOptions}.
+     *
+     * Command Response - A 2D-`array` of 4-tuples containing extended message information.
+     * See example of {@link BaseClient.xpendingWithOptions|xpendingWithOptions} for more details.
+     */
+    public xpendingWithOptions(
+        key: string,
+        group: string,
+        options: StreamPendingOptions,
+    ): T {
+        return this.addAndReturn(createXPending(key, group, options));
+    }
+
+    /**
+     * Returns the list of all consumers and their attributes for the given consumer group of the
+     * stream stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/xinfo-consumers/|valkey.io} for details.
+     *
+     * Command Response - An `Array` of `Records`, where each mapping contains the attributes
+     *     of a consumer for the given consumer group of the stream at `key`.
+     */
+    public xinfoConsumers(key: string, group: string): T {
+        return this.addAndReturn(createXInfoConsumers(key, group));
+    }
+
+    /**
+     * Changes the ownership of a pending message.
+     *
+     * @see {@link https://valkey.io/commands/xclaim/|valkey.io} for details.
+     *
+     * @param key - The key of the stream.
+     * @param group - The consumer group name.
+     * @param consumer - The group consumer.
+     * @param minIdleTime - The minimum idle time for the message to be claimed.
+     * @param ids - An array of entry ids.
+     * @param options - (Optional) Stream claim options {@link StreamClaimOptions}.
+     *
+     * Command Response - A `Record` of message entries that are claimed by the consumer.
+     */
+    public xclaim(
+        key: string,
+        group: string,
+        consumer: string,
+        minIdleTime: number,
+        ids: string[],
+        options?: StreamClaimOptions,
+    ): T {
+        return this.addAndReturn(
+            createXClaim(key, group, consumer, minIdleTime, ids, options),
+        );
+    }
+
+    /**
+     * Changes the ownership of a pending message. This function returns an `array` with
+     * only the message/entry IDs, and is equivalent to using `JUSTID` in the Valkey API.
+     *
+     * @see {@link https://valkey.io/commands/xclaim/|valkey.io} for details.
+     *
+     * @param key - The key of the stream.
+     * @param group - The consumer group name.
+     * @param consumer - The group consumer.
+     * @param minIdleTime - The minimum idle time for the message to be claimed.
+     * @param ids - An array of entry ids.
+     * @param options - (Optional) Stream claim options {@link StreamClaimOptions}.
+     *
+     * Command Response - An `array` of message ids claimed by the consumer.
+     */
+    public xclaimJustId(
+        key: string,
+        group: string,
+        consumer: string,
+        minIdleTime: number,
+        ids: string[],
+        options?: StreamClaimOptions,
+    ): T {
+        return this.addAndReturn(
+            createXClaim(key, group, consumer, minIdleTime, ids, options, true),
+        );
+    }
+
+    /**
+     * Transfers ownership of pending stream entries that match the specified criteria.
+     *
+     * @see {@link https://valkey.io/commands/xautoclaim/|valkey.io} for more details.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param key - The key of the stream.
+     * @param group - The consumer group name.
+     * @param consumer - The group consumer.
+     * @param minIdleTime - The minimum idle time for the message to be claimed.
+     * @param start - Filters the claimed entries to those that have an ID equal or greater than the
+     *     specified value.
+     * @param count - (Optional) Limits the number of claimed entries to the specified value.
+     *
+     * Command Response - An `array` containing the following elements:
+     *   - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is
+     *     equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if
+     *     the entire stream was scanned.
+     *   - A mapping of the claimed entries.
+     *   - If you are using Valkey 7.0.0 or above, the response list will also include a list containing
+     *     the message IDs that were in the Pending Entries List but no longer exist in the stream.
+     *     These IDs are deleted from the Pending Entries List.
+     */
+    public xautoclaim(
+        key: string,
+        group: string,
+        consumer: string,
+        minIdleTime: number,
+        start: string,
+        count?: number,
+    ): T {
+        return this.addAndReturn(
+            createXAutoClaim(key, group, consumer, minIdleTime, start, count),
+        );
+    }
+
+    /**
+     * Transfers ownership of pending stream entries that match the specified criteria.
+     *
+     * @see {@link https://valkey.io/commands/xautoclaim/|valkey.io} for more details.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param key - The key of the stream.
+     * @param group - The consumer group name.
+     * @param consumer - The group consumer.
+     * @param minIdleTime - The minimum idle time for the message to be claimed.
+     * @param start - Filters the claimed entries to those that have an ID equal or greater than the
+     *     specified value.
+     * @param count - (Optional) Limits the number of claimed entries to the specified value.
+     *
+     * Command Response - An `array` containing the following elements:
+     *   - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is
+     *     equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if
+     *     the entire stream was scanned.
+     *   - A list of the IDs for the claimed entries.
+     *   - If you are using Valkey 7.0.0 or above, the response list will also include a list containing
+     *     the message IDs that were in the Pending Entries List but no longer exist in the stream.
+     *     These IDs are deleted from the Pending Entries List.
+     */
+    public xautoclaimJustId(
+        key: string,
+        group: string,
+        consumer: string,
+        minIdleTime: number,
+        start: string,
+        count?: number,
+    ): T {
+        return this.addAndReturn(
+            createXAutoClaim(
+                key,
+                group,
+                consumer,
+                minIdleTime,
+                start,
+                count,
+                true,
+            ),
+        );
+    }
+
     /**
      * Creates a new consumer group uniquely identified by `groupname` for the stream
      * stored at `key`.
      *
-     * See https://valkey.io/commands/xgroup-create/ for more details.
+     * @see {@link https://valkey.io/commands/xgroup-create/|valkey.io} for details.
      *
      * @param key - The key of the stream.
      * @param groupName - The newly created consumer group name.
@@ -2155,7 +2628,7 @@ export class BaseTransaction> {
     /**
      * Destroys the consumer group `groupname` for the stream stored at `key`.
      *
-     * See https://valkey.io/commands/xgroup-destroy/ for more details.
+     * @see {@link https://valkey.io/commands/xgroup-destroy/|valkey.io} for details.
      *
      * @param key - The key of the stream.
      * @param groupname - The newly created consumer group name.
@@ -2166,15 +2639,59 @@ export class BaseTransaction> {
         return this.addAndReturn(createXGroupDestroy(key, groupName));
     }
 
+    /**
+     * Creates a consumer named `consumerName` in the consumer group `groupName` for the stream stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/xgroup-createconsumer/|valkey.io} for more details.
+     *
+     * @param key - The key of the stream.
+     * @param groupName - The consumer group name.
+     * @param consumerName - The newly created consumer.
+     *
+     * Command Response - `true` if the consumer is created. Otherwise, returns `false`.
+     */
+    public xgroupCreateConsumer(
+        key: string,
+        groupName: string,
+        consumerName: string,
+    ): T {
+        return this.addAndReturn(
+            createXGroupCreateConsumer(key, groupName, consumerName),
+        );
+    }
+
+    /**
+     * Deletes a consumer named `consumerName` in the consumer group `groupName` for the stream stored at `key`.
+     *
+     * @see {@link https://valkey.io/commands/xgroup-delconsumer/|valkey.io} for more details.
+     *
+     * @param key - The key of the stream.
+     * @param groupName - The consumer group name.
+     * @param consumerName - The consumer to delete.
+     *
+     * Command Response - The number of pending messages the `consumer` had before it was deleted.
+     */
+    public xgroupDelConsumer(
+        key: string,
+        groupName: string,
+        consumerName: string,
+    ): T {
+        return this.addAndReturn(
+            createXGroupDelConsumer(key, groupName, consumerName),
+        );
+    }
+
     /**
      * Renames `key` to `newkey`.
      * If `newkey` already exists it is overwritten.
      * In Cluster mode, both `key` and `newkey` must be in the same hash slot,
      * meaning that in practice only keys that have the same hash tag can be reliably renamed in cluster.
-     * See https://valkey.io/commands/rename/ for more details.
+     *
+     * @see {@link https://valkey.io/commands/rename/|valkey.io} for details.
      *
      * @param key - The key to rename.
      * @param newKey - The new name of the key.
+     *
      * Command Response - If the `key` was successfully renamed, return "OK". If `key` does not exist, an error is thrown.
      */
     public rename(key: string, newKey: string): T {
@@ -2185,7 +2702,8 @@ export class BaseTransaction> {
      * Renames `key` to `newkey` if `newkey` does not yet exist.
      * In Cluster mode, both `key` and `newkey` must be in the same hash slot,
      * meaning that in practice only keys that have the same hash tag can be reliably renamed in cluster.
-     * See https://valkey.io/commands/renamenx/ for more details.
+     *
+     * @see {@link https://valkey.io/commands/renamenx/|valkey.io} for details.
      *
      * @param key - The key to rename.
      * @param newKey - The new name of the key.
@@ -2200,12 +2718,13 @@ export class BaseTransaction> {
      * Pop an element from the tail of the first list that is non-empty,
      * with the given `keys` being checked in the order that they are given.
      * Blocks the connection when there are no elements to pop from any of the given lists.
-     * See https://valkey.io/commands/brpop/ for more details.
-     * Note: `BRPOP` is a blocking command,
-     * see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices.
+     *
+     * @see {@link https://valkey.io/commands/brpop/|valkey.io} for details.
+     * @remarks `BRPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices.
      *
      * @param keys - The `keys` of the lists to pop from.
      * @param timeout - The `timeout` in seconds.
+     *
      * Command Response - An `array` containing the `key` from which the element was popped and the value of the popped element,
      * formatted as [key, value]. If no element could be popped and the timeout expired, returns `null`.
      */
@@ -2217,12 +2736,13 @@ export class BaseTransaction> {
      * Pop an element from the head of the first list that is non-empty,
      * with the given `keys` being checked in the order that they are given.
      * Blocks the connection when there are no elements to pop from any of the given lists.
-     * See https://valkey.io/commands/blpop/ for more details.
-     * Note: `BLPOP` is a blocking command,
-     * see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices.
+     *
+     * @see {@link https://valkey.io/commands/blpop/|valkey.io} for details.
+     * @remarks `BLPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices.
      *
      * @param keys - The `keys` of the lists to pop from.
      * @param timeout - The `timeout` in seconds.
+     *
      * Command Response - An `array` containing the `key` from which the element was popped and the value of the popped element,
      * formatted as [key, value]. If no element could be popped and the timeout expired, returns `null`.
      */
@@ -2234,7 +2754,7 @@ export class BaseTransaction> {
      * Creates a new structure if the `key` does not exist.
      * When no elements are provided, and `key` exists and is a HyperLogLog, then no operation is performed.
      *
-     * See https://valkey.io/commands/pfadd/ for more details.
+     * @see {@link https://valkey.io/commands/pfadd/|valkey.io} for details.
      *
      * @param key - The key of the HyperLogLog data structure to add elements into.
      * @param elements - An array of members to add to the HyperLogLog stored at `key`.
@@ -2248,7 +2768,7 @@ export class BaseTransaction> {
     /** Estimates the cardinality of the data stored in a HyperLogLog structure for a single key or
      * calculates the combined cardinality of multiple keys by merging their HyperLogLogs temporarily.
      *
-     * See https://valkey.io/commands/pfcount/ for more details.
+     * @see {@link https://valkey.io/commands/pfcount/|valkey.io} for details.
      *
      * @param keys - The keys of the HyperLogLog data structures to be analyzed.
      * Command Response - The approximated cardinality of given HyperLogLog data structures.
@@ -2262,7 +2782,7 @@ export class BaseTransaction> {
      * Merges multiple HyperLogLog values into a unique value. If the destination variable exists, it is
      * treated as one of the source HyperLogLog data sets, otherwise a new HyperLogLog is created.
      *
-     * See https://valkey.io/commands/pfmerge/ for more details.
+     * @see {@link https://valkey.io/commands/pfmerge/|valkey.io} for details.
      *
      * @param destination - The key of the destination HyperLogLog where the merged data sets will be stored.
      * @param sourceKeys - The keys of the HyperLogLog structures to be merged.
@@ -2274,7 +2794,7 @@ export class BaseTransaction> {
 
     /** Returns the internal encoding for the Redis object stored at `key`.
      *
-     * See https://valkey.io/commands/object-encoding for more details.
+     * @see {@link https://valkey.io/commands/object-encoding/|valkey.io} for more details.
      *
      * @param key - The `key` of the object to get the internal encoding of.
      * Command Response - If `key` exists, returns the internal encoding of the object stored at `key` as a string.
@@ -2286,7 +2806,7 @@ export class BaseTransaction> {
 
     /** Returns the logarithmic access frequency counter of a Redis object stored at `key`.
      *
-     * See https://valkey.io/commands/object-freq for more details.
+     * @see {@link https://valkey.io/commands/object-freq/|valkey.io} for more details.
      *
      * @param key - The `key` of the object to get the logarithmic access frequency counter of.
      * Command Response - If `key` exists, returns the logarithmic access frequency counter of
@@ -2299,7 +2819,7 @@ export class BaseTransaction> {
     /**
      * Returns the time in seconds since the last access to the value stored at `key`.
      *
-     * See https://valkey.io/commands/object-idletime/ for more details.
+     * @see {@link https://valkey.io/commands/object-idletime/|valkey.io} for details.
      *
      * @param key - The key of the object to get the idle time of.
      *
@@ -2312,7 +2832,7 @@ export class BaseTransaction> {
     /**
      * Returns the reference count of the object stored at `key`.
      *
-     * See https://valkey.io/commands/object-refcount/ for more details.
+     * @see {@link https://valkey.io/commands/object-refcount/|valkey.io} for details.
      *
      * @param key - The `key` of the object to get the reference count of.
      *
@@ -2326,7 +2846,7 @@ export class BaseTransaction> {
     /**
      * Displays a piece of generative computer art and the server version.
      *
-     * See https://valkey.io/commands/lolwut/ for more details.
+     * @see {@link https://valkey.io/commands/lolwut/|valkey.io} for details.
      *
      * @param options - The LOLWUT options.
      *
@@ -2337,11 +2857,27 @@ export class BaseTransaction> {
     }
 
     /**
-     * Invokes a previously loaded function.
+     * Blocks the current client until all the previous write commands are successfully transferred and
+     * acknowledged by at least `numreplicas` of replicas. If `timeout` is reached, the command returns
+     * the number of replicas that were not yet reached.
      *
-     * See https://valkey.io/commands/fcall/ for more details.
+     * @see {@link https://valkey.io/commands/wait/|valkey.io} for more details.
      *
-     * since Valkey version 7.0.0.
+     * @param numreplicas - The number of replicas to reach.
+     * @param timeout - The timeout value specified in milliseconds. A value of 0 will block indefinitely.
+     *
+     * Command Response - The number of replicas reached by all the writes performed in the context of the
+     *     current connection.
+     */
+    public wait(numreplicas: number, timeout: number): T {
+        return this.addAndReturn(createWait(numreplicas, timeout));
+    }
+
+    /**
+     * Invokes a previously loaded function.
+     *
+     * @see {@link https://valkey.io/commands/fcall/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param func - The function name.
      * @param keys - A list of `keys` accessed by the function. To ensure the correct execution of functions,
@@ -2357,9 +2893,8 @@ export class BaseTransaction> {
     /**
      * Invokes a previously loaded read-only function.
      *
-     * See https://valkey.io/commands/fcall/ for more details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/fcall/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param func - The function name.
      * @param keys - A list of `keys` accessed by the function. To ensure the correct execution of functions,
@@ -2375,9 +2910,8 @@ export class BaseTransaction> {
     /**
      * Deletes a library and all its functions.
      *
-     * See https://valkey.io/commands/function-delete/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-delete/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param libraryCode - The library name to delete.
      *
@@ -2390,9 +2924,8 @@ export class BaseTransaction> {
     /**
      * Loads a library to Valkey.
      *
-     * See https://valkey.io/commands/function-load/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-load/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param libraryCode - The source code that implements the library.
      * @param replace - Whether the given library should overwrite a library with the same name if it
@@ -2407,9 +2940,8 @@ export class BaseTransaction> {
     /**
      * Deletes all function libraries.
      *
-     * See https://valkey.io/commands/function-flush/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-flush/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param mode - The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}.
      * Command Response - `OK`.
@@ -2421,9 +2953,8 @@ export class BaseTransaction> {
     /**
      * Returns information about the functions and libraries.
      *
-     * See https://valkey.io/commands/function-list/ for details.
-     *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/function-list/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param options - Parameters to filter and request additional info.
      *
@@ -2433,10 +2964,26 @@ export class BaseTransaction> {
         return this.addAndReturn(createFunctionList(options));
     }
 
+    /**
+     * Returns information about the function that's currently running and information about the
+     * available execution engines.
+     *
+     * @see {@link https://valkey.io/commands/function-stats/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
+     *
+     * Command Response - A `Record` of type {@link FunctionStatsResponse} with two keys:
+     *
+     * - `"running_script"` with information about the running script.
+     * - `"engines"` with information about available engines and their stats.
+     */
+    public functionStats(): T {
+        return this.addAndReturn(createFunctionStats());
+    }
+
     /**
      * Deletes all the keys of all the existing databases. This command never fails.
      *
-     * See https://valkey.io/commands/flushall/ for more details.
+     * @see {@link https://valkey.io/commands/flushall/|valkey.io} for details.
      *
      * @param mode - The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}.
      *
@@ -2449,7 +2996,7 @@ export class BaseTransaction> {
     /**
      * Deletes all the keys of the currently selected database. This command never fails.
      *
-     * See https://valkey.io/commands/flushdb/ for more details.
+     * @see {@link https://valkey.io/commands/flushdb/|valkey.io} for details.
      *
      * @param mode - The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}.
      *
@@ -2464,7 +3011,8 @@ export class BaseTransaction> {
      * match is found, `null` is returned. If the `count` option is specified, then the function returns
      * an `array` of indices of matching elements within the list.
      *
-     * See https://valkey.io/commands/lpos/ for more details.
+     * @see {@link https://valkey.io/commands/lpos/|valkey.io} for details.
+     * @remarks Since Valkey version 6.0.6.
      *
      * @param key - The name of the list.
      * @param element - The value to search for within the list.
@@ -2472,8 +3020,6 @@ export class BaseTransaction> {
      *
      * Command Response - The index of `element`, or `null` if `element` is not in the list. If the `count`
      * option is specified, then the function returns an `array` of indices of matching elements within the list.
-     *
-     * since - Valkey version 6.0.6.
      */
     public lpos(key: string, element: string, options?: LPosOptions): T {
         return this.addAndReturn(createLPos(key, element, options));
@@ -2482,7 +3028,7 @@ export class BaseTransaction> {
     /**
      * Returns the number of keys in the currently selected database.
      *
-     * See https://valkey.io/commands/dbsize/ for more details.
+     * @see {@link https://valkey.io/commands/dbsize/|valkey.io} for details.
      *
      * Command Response - The number of keys in the currently selected database.
      */
@@ -2494,7 +3040,7 @@ export class BaseTransaction> {
      * Counts the number of set bits (population counting) in the string stored at `key`. The `options` argument can
      * optionally be provided to count the number of bits in a specific string interval.
      *
-     * See https://valkey.io/commands/bitcount for more details.
+     * @see {@link https://valkey.io/commands/bitcount/|valkey.io} for more details.
      *
      * @param key - The key for the string to count the set bits of.
      * @param options - The offset options.
@@ -2511,7 +3057,7 @@ export class BaseTransaction> {
      * Adds geospatial members with their positions to the specified sorted set stored at `key`.
      * If a member is already a part of the sorted set, its position is updated.
      *
-     * See https://valkey.io/commands/geoadd/ for more details.
+     * @see {@link https://valkey.io/commands/geoadd/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param membersToGeospatialData - A mapping of member names to their corresponding positions - see
@@ -2536,9 +3082,8 @@ export class BaseTransaction> {
      * Returns the members of a sorted set populated with geospatial information using {@link geoadd},
      * which are within the borders of the area specified by a given shape.
      *
-     * See https://valkey.io/commands/geosearch/ for more details.
-     *
-     * since - Valkey 6.2.0 and above.
+     * @see {@link https://valkey.io/commands/geosearch/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param key - The key of the sorted set.
      * @param searchFrom - The query's center point options, could be one of:
@@ -2575,11 +3120,52 @@ export class BaseTransaction> {
         );
     }
 
+    /**
+     * Searches for members in a sorted set stored at `source` representing geospatial data
+     * within a circular or rectangular area and stores the result in `destination`.
+     *
+     * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created.
+     *
+     * To get the result directly, see {@link geosearch}.
+     *
+     * @see {@link https://valkey.io/commands/geosearchstore/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
+     *
+     * @param destination - The key of the destination sorted set.
+     * @param source - The key of the sorted set.
+     * @param searchFrom - The query's center point options, could be one of:
+     * - {@link MemberOrigin} to use the position of the given existing member in the sorted set.
+     * - {@link CoordOrigin} to use the given longitude and latitude coordinates.
+     * @param searchBy - The query's shape options, could be one of:
+     * - {@link GeoCircleShape} to search inside circular area according to given radius.
+     * - {@link GeoBoxShape} to search inside an axis-aligned rectangle, determined by height and width.
+     * @param resultOptions - (Optional) Parameters to request additional information and configure sorting/limiting the results, see {@link GeoSearchStoreResultOptions}.
+     *
+     * Command Response - The number of elements in the resulting sorted set stored at `destination`.
+     */
+    public geosearchstore(
+        destination: string,
+        source: string,
+        searchFrom: SearchOrigin,
+        searchBy: GeoSearchShape,
+        resultOptions?: GeoSearchStoreResultOptions,
+    ): T {
+        return this.addAndReturn(
+            createGeoSearchStore(
+                destination,
+                source,
+                searchFrom,
+                searchBy,
+                resultOptions,
+            ),
+        );
+    }
+
     /**
      * Returns the positions (longitude, latitude) of all the specified `members` of the
      * geospatial index represented by the sorted set at `key`.
      *
-     * See https://valkey.io/commands/geopos for more details.
+     * @see {@link https://valkey.io/commands/geopos/|valkey.io} for more details.
      *
      * @param key - The key of the sorted set.
      * @param members - The members for which to get the positions.
@@ -2596,7 +3182,8 @@ export class BaseTransaction> {
      * Pops a member-score pair from the first non-empty sorted set, with the given `keys`
      * being checked in the order they are provided.
      *
-     * See https://valkey.io/commands/zmpop/ for more details.
+     * @see {@link https://valkey.io/commands/zmpop/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param keys - The keys of the sorted sets.
      * @param modifier - The element pop criteria - either {@link ScoreFilter.MIN} or
@@ -2606,8 +3193,6 @@ export class BaseTransaction> {
      * Command Response - A two-element `array` containing the key name of the set from which the
      *     element was popped, and a member-score `Record` of the popped element.
      *     If no member could be popped, returns `null`.
-     *
-     * since Valkey version 7.0.0.
      */
     public zmpop(keys: string[], modifier: ScoreFilter, count?: number): T {
         return this.addAndReturn(createZMPop(keys, modifier, count));
@@ -2618,10 +3203,10 @@ export class BaseTransaction> {
      * checked in the order they are provided. Blocks the connection when there are no members
      * to pop from any of the given sorted sets. `BZMPOP` is the blocking variant of {@link zmpop}.
      *
-     * See https://valkey.io/commands/bzmpop/ for more details.
+     * @see {@link https://valkey.io/commands/bzmpop/|valkey.io} for details.
+     * @remarks `BZMPOP` is a client blocking command, see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands | Valkey Glide Wiki} for more details and best practices.
+     * @remarks Since Valkey version 7.0.0.
      *
-     * @remarks `BZMPOP` is a client blocking command, see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands | the wiki}
-     * for more details and best practices.
      * @param keys - The keys of the sorted sets.
      * @param modifier - The element pop criteria - either {@link ScoreFilter.MIN} or
      *     {@link ScoreFilter.MAX} to pop the member with the lowest/highest score accordingly.
@@ -2631,8 +3216,6 @@ export class BaseTransaction> {
      * Command Response - A two-element `array` containing the key name of the set from which the element
      *     was popped, and a member-score `Record` of the popped element.
      *     If no member could be popped, returns `null`.
-     *
-     * since Valkey version 7.0.0.
      */
     public bzmpop(
         keys: string[],
@@ -2648,7 +3231,7 @@ export class BaseTransaction> {
      * If `member` does not exist in the sorted set, it is added with `increment` as its score.
      * If `key` does not exist, a new sorted set is created with the specified member as its sole member.
      *
-     * See https://valkey.io/commands/zincrby/ for details.
+     * @see {@link https://valkey.io/commands/zincrby/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param increment - The score increment.
@@ -2663,7 +3246,7 @@ export class BaseTransaction> {
     /**
      * Iterates incrementally over a sorted set.
      *
-     * See https://valkey.io/commands/zscan for more details.
+     * @see {@link https://valkey.io/commands/zscan/|valkey.io} for more details.
      *
      * @param key - The key of the sorted set.
      * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of
@@ -2683,7 +3266,7 @@ export class BaseTransaction> {
     /**
      * Returns the distance between `member1` and `member2` saved in the geospatial index stored at `key`.
      *
-     * See https://valkey.io/commands/geodist/ for more details.
+     * @see {@link https://valkey.io/commands/geodist/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param member1 - The name of the first member.
@@ -2705,7 +3288,7 @@ export class BaseTransaction> {
     /**
      * Returns the `GeoHash` strings representing the positions of all the specified `members` in the sorted set stored at `key`.
      *
-     * See https://valkey.io/commands/geohash/ for more details.
+     * @see {@link https://valkey.io/commands/geohash/|valkey.io} for details.
      *
      * @param key - The key of the sorted set.
      * @param members - The array of members whose `GeoHash` strings are to be retrieved.
@@ -2721,7 +3304,7 @@ export class BaseTransaction> {
      * Returns `UNIX TIME` of the last DB save timestamp or startup timestamp if no save
      * was made since then.
      *
-     * See https://valkey.io/commands/lastsave/ for more details.
+     * @see {@link https://valkey.io/commands/lastsave/|valkey.io} for details.
      *
      * Command Response - `UNIX TIME` of the last DB save executed with success.
      */
@@ -2732,9 +3315,8 @@ export class BaseTransaction> {
     /**
      * Returns all the longest common subsequences combined between strings stored at `key1` and `key2`.
      *
-     * since Valkey version 7.0.0.
-     *
-     * See https://valkey.io/commands/lcs/ for more details.
+     * @see {@link https://valkey.io/commands/lcs/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key1 - The key that stores the first string.
      * @param key2 - The key that stores the second string.
@@ -2749,9 +3331,8 @@ export class BaseTransaction> {
     /**
      * Returns the total length of all the longest common subsequences between strings stored at `key1` and `key2`.
      *
-     * since Valkey version 7.0.0.
-     *
-     * See https://valkey.io/commands/lcs/ for more details.
+     * @see {@link https://valkey.io/commands/lcs/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key1 - The key that stores the first string.
      * @param key2 - The key that stores the second string.
@@ -2766,9 +3347,8 @@ export class BaseTransaction> {
      * Returns the indices and lengths of the longest common subsequences between strings stored at
      * `key1` and `key2`.
      *
-     * since Valkey version 7.0.0.
-     *
-     * See https://valkey.io/commands/lcs/ for more details.
+     * @see {@link https://valkey.io/commands/lcs/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key1 - The key that stores the first string.
      * @param key2 - The key that stores the second string.
@@ -2783,6 +3363,8 @@ export class BaseTransaction> {
      *     - `"matches"` is mapped to a three dimensional array of integers that stores pairs
      *           of indices that represent the location of the common subsequences in the strings held
      *           by `key1` and `key2`.
+     *
+     *     See example of {@link BaseClient.lcsIdx|lcsIdx} for more details.
      */
     public lcsIdx(
         key1: string,
@@ -2795,7 +3377,7 @@ export class BaseTransaction> {
     /**
      * Updates the last access time of the specified keys.
      *
-     * See https://valkey.io/commands/touch/ for more details.
+     * @see {@link https://valkey.io/commands/touch/|valkey.io} for details.
      *
      * @param keys - The keys to update the last access time of.
      *
@@ -2808,7 +3390,7 @@ export class BaseTransaction> {
     /**
      * Returns a random existing key name from the currently selected database.
      *
-     * See https://valkey.io/commands/randomkey/ for more details.
+     * @see {@link https://valkey.io/commands/randomkey/|valkey.io} for details.
      *
      * Command Response - A random existing key name from the currently selected database.
      */
@@ -2821,7 +3403,7 @@ export class BaseTransaction> {
      * for the entire length of `value`. If the `offset` is larger than the current length of the string at `key`,
      * the string is padded with zero bytes to make `offset` fit. Creates the `key` if it doesn't exist.
      *
-     * See https://valkey.io/commands/setrange/ for more details.
+     * @see {@link https://valkey.io/commands/setrange/|valkey.io} for details.
      *
      * @param key - The key of the string to update.
      * @param offset - The position in the string where `value` should be written.
@@ -2833,19 +3415,32 @@ export class BaseTransaction> {
         return this.addAndReturn(createSetRange(key, offset, value));
     }
 
+    /**
+     * Appends a `value` to a `key`. If `key` does not exist it is created and set as an empty string,
+     * so `APPEND` will be similar to {@link set} in this special case.
+     *
+     * @see {@link https://valkey.io/commands/append/|valkey.io} for details.
+     *
+     * @param key - The key of the string.
+     * @param value - The key of the string.
+     *
+     * Command Response - The length of the string after appending the value.
+     */
+    public append(key: string, value: string): T {
+        return this.addAndReturn(createAppend(key, value));
+    }
+
     /**
      * Pops one or more elements from the first non-empty list from the provided `keys`.
      *
-     * See https://valkey.io/commands/lmpop/ for more details.
+     * @see {@link https://valkey.io/commands/lmpop/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
-     * @remarks When in cluster mode, `source` and `destination` must map to the same hash slot.
      * @param keys - An array of keys to lists.
      * @param direction - The direction based on which elements are popped from - see {@link ListDirection}.
      * @param count - (Optional) The maximum number of popped elements.
      *
      * Command Response - A `Record` of `key` name mapped array of popped elements.
-     *
-     * since Valkey version 7.0.0.
      */
     public lmpop(keys: string[], direction: ListDirection, count?: number): T {
         return this.addAndReturn(createLMPop(keys, direction, count));
@@ -2855,7 +3450,8 @@ export class BaseTransaction> {
      * Blocks the connection until it pops one or more elements from the first non-empty list from the
      * provided `key`. `BLMPOP` is the blocking variant of {@link lmpop}.
      *
-     * See https://valkey.io/commands/blmpop/ for more details.
+     * @see {@link https://valkey.io/commands/blmpop/|valkey.io} for details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param keys - An array of keys to lists.
      * @param direction - The direction based on which elements are popped from - see {@link ListDirection}.
@@ -2865,8 +3461,6 @@ export class BaseTransaction> {
      *
      * Command Response - A `Record` of `key` name mapped array of popped elements.
      *     If no member could be popped and the timeout expired, returns `null`.
-     *
-     * since Valkey version 7.0.0.
      */
     public blmpop(
         keys: string[],
@@ -2881,7 +3475,7 @@ export class BaseTransaction> {
      * Lists the currently active channels.
      * The command is routed to all nodes, and aggregates the response to a single array.
      *
-     * See https://valkey.io/commands/pubsub-channels for more details.
+     * @see {@link https://valkey.io/commands/pubsub-channels/|valkey.io} for more details.
      *
      * @param pattern - A glob-style pattern to match active channels.
      *                  If not provided, all active channels are returned.
@@ -2899,7 +3493,7 @@ export class BaseTransaction> {
      * not the count of clients subscribed to patterns.
      * The command is routed to all nodes, and aggregates the response to the sum of all pattern subscriptions.
      *
-     * See https://valkey.io/commands/pubsub-numpat for more details.
+     * @see {@link https://valkey.io/commands/pubsub-numpat/|valkey.io} for more details.
      *
      * Command Response - The number of unique patterns.
      */
@@ -2913,7 +3507,7 @@ export class BaseTransaction> {
      * Note that it is valid to call this command without channels. In this case, it will just return an empty map.
      * The command is routed to all nodes, and aggregates the response to a single map of the channels and their number of subscriptions.
      *
-     * See https://valkey.io/commands/pubsub-numsub for more details.
+     * @see {@link https://valkey.io/commands/pubsub-numsub/|valkey.io} for more details.
      *
      * @param channels - The list of channels to query for the number of subscribers.
      *                   If not provided, returns an empty map.
@@ -2948,7 +3542,7 @@ export class Transaction extends BaseTransaction {
     /// TODO: add MOVE, SLAVEOF and all SENTINEL commands
 
     /** Change the currently selected Redis database.
-     * See https://valkey.io/commands/select/ for details.
+     * @see {@link https://valkey.io/commands/select/|valkey.io} for details.
      *
      * @param index - The index of the database to select.
      *
@@ -2966,7 +3560,7 @@ export class Transaction extends BaseTransaction {
      *
      * To store the result into a new key, see {@link sortStore}.
      *
-     * See https://valkey.io/commands/sort for more details.
+     * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details.
      *
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param options - (Optional) {@link SortOptions}.
@@ -2985,7 +3579,7 @@ export class Transaction extends BaseTransaction {
      *
      * This command is routed depending on the client's {@link ReadFrom} strategy.
      *
-     * since Valkey version 7.0.0.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param options - (Optional) {@link SortOptions}.
@@ -3005,9 +3599,8 @@ export class Transaction extends BaseTransaction {
      *
      * To get the sort result without storing it into a key, see {@link sort} or {@link sortReadOnly}.
      *
-     * See https://valkey.io/commands/sort for more details.
+     * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details.
      *
-     * @remarks When in cluster mode, `destination` and `key` must map to the same hash slot.
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param destination - The key where the sorted result will be stored.
      * @param options - (Optional) {@link SortOptions}.
@@ -3028,7 +3621,8 @@ export class Transaction extends BaseTransaction {
      * When `replace` is true, removes the `destination` key first if it already exists, otherwise performs
      * no action.
      *
-     * See https://valkey.io/commands/copy/ for more details.
+     * @see {@link https://valkey.io/commands/copy/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param source - The key to the source value.
      * @param destination - The key where the value should be copied to.
@@ -3038,8 +3632,6 @@ export class Transaction extends BaseTransaction {
      *     value to it. If not provided, no action will be performed if the key already exists.
      *
      * Command Response - `true` if `source` was copied, `false` if the `source` was not copied.
-     *
-     * since Valkey version 6.2.0.
      */
     public copy(
         source: string,
@@ -3049,9 +3641,24 @@ export class Transaction extends BaseTransaction {
         return this.addAndReturn(createCopy(source, destination, options));
     }
 
+    /**
+     * Move `key` from the currently selected database to the database specified by `dbIndex`.
+     *
+     * @see {@link https://valkey.io/commands/move/|valkey.io} for details.
+     *
+     * @param key - The key to move.
+     * @param dbIndex - The index of the database to move `key` to.
+     *
+     * Command Response - `true` if `key` was moved, or `false` if the `key` already exists in the destination
+     *     database or does not exist in the source database.
+     */
+    public move(key: string, dbIndex: number): Transaction {
+        return this.addAndReturn(createMove(key, dbIndex));
+    }
+
     /** Publish a message on pubsub channel.
      *
-     * See https://valkey.io/commands/publish for more details.
+     * @see {@link https://valkey.io/commands/publish/|valkey.io} for more details.
      *
      * @param message - Message to publish.
      * @param channel - Channel to publish the message on.
@@ -3086,7 +3693,7 @@ export class ClusterTransaction extends BaseTransaction {
      *
      * To store the result into a new key, see {@link sortStore}.
      *
-     * See https://valkey.io/commands/sort for more details.
+     * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details.
      *
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param options - (Optional) {@link SortClusterOptions}.
@@ -3105,7 +3712,8 @@ export class ClusterTransaction extends BaseTransaction {
      *
      * This command is routed depending on the client's {@link ReadFrom} strategy.
      *
-     * since Valkey version 7.0.0.
+     * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details.
+     * @remarks Since Valkey version 7.0.0.
      *
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param options - (Optional) {@link SortClusterOptions}.
@@ -3128,9 +3736,8 @@ export class ClusterTransaction extends BaseTransaction {
      *
      * To get the sort result without storing it into a key, see {@link sort} or {@link sortReadOnly}.
      *
-     * See https://valkey.io/commands/sort for more details.
+     * @see {@link https://valkey.io/commands/sort|valkey.io} for more details.
      *
-     * @remarks When in cluster mode, `destination` and `key` must map to the same hash slot.
      * @param key - The key of the list, set, or sorted set to be sorted.
      * @param destination - The key where the sorted result will be stored.
      * @param options - (Optional) {@link SortClusterOptions}.
@@ -3149,7 +3756,8 @@ export class ClusterTransaction extends BaseTransaction {
      * Copies the value stored at the `source` to the `destination` key. When `replace` is true,
      * removes the `destination` key first if it already exists, otherwise performs no action.
      *
-     * See https://valkey.io/commands/copy/ for more details.
+     * @see {@link https://valkey.io/commands/copy/|valkey.io} for details.
+     * @remarks Since Valkey version 6.2.0.
      *
      * @param source - The key to the source value.
      * @param destination - The key where the value should be copied to.
@@ -3157,8 +3765,6 @@ export class ClusterTransaction extends BaseTransaction {
      *     value to it. If not provided, no action will be performed if the key already exists.
      *
      * Command Response - `true` if `source` was copied, `false` if the `source` was not copied.
-     *
-     * since Valkey version 6.2.0.
      */
     public copy(
         source: string,
@@ -3175,7 +3781,7 @@ export class ClusterTransaction extends BaseTransaction {
      * The mode is selected using the 'sharded' parameter.
      * For both sharded and non-sharded mode, request is routed using hashed channel as key.
      *
-     * See https://valkey.io/commands/publish and https://valkey.io/commands/spublish for more details.
+     * @see {@link https://valkey.io/commands/publish} and {@link https://valkey.io/commands/spublish} for more details.
      *
      * @param message - Message to publish.
      * @param channel - Channel to publish the message on.
@@ -3195,7 +3801,7 @@ export class ClusterTransaction extends BaseTransaction {
      * Lists the currently active shard channels.
      * The command is routed to all nodes, and aggregates the response to a single array.
      *
-     * See https://valkey.io/commands/pubsub-shardchannels for more details.
+     * @see {@link https://valkey.io/commands/pubsub-shardchannels|valkey.io} for more details.
      *
      * @param pattern - A glob-style pattern to match active shard channels.
      *                  If not provided, all active shard channels are returned.
@@ -3212,7 +3818,7 @@ export class ClusterTransaction extends BaseTransaction {
      * Note that it is valid to call this command without channels. In this case, it will just return an empty map.
      * The command is routed to all nodes, and aggregates the response to a single map of the channels and their number of subscriptions.
      *
-     * See https://valkey.io/commands/pubsub-shardnumsub for more details.
+     * @see {@link https://valkey.io/commands/pubsub-shardnumsub|valkey.io} for more details.
      *
      * @param channels - The list of shard channels to query for the number of subscribers.
      *                   If not provided, returns an empty map.
diff --git a/node/tests/GlideClient.test.ts b/node/tests/GlideClient.test.ts
index 11b75ef82e..48d1035603 100644
--- a/node/tests/GlideClient.test.ts
+++ b/node/tests/GlideClient.test.ts
@@ -13,6 +13,7 @@ import {
 import { BufferReader, BufferWriter } from "protobufjs";
 import { v4 as uuidv4 } from "uuid";
 import {
+    Decoder,
     GlideClient,
     ListDirection,
     ProtocolVersion,
@@ -25,15 +26,19 @@ import { command_request } from "../src/ProtobufMessage";
 import { runBaseTests } from "./SharedTests";
 import {
     checkFunctionListResponse,
+    checkFunctionStatsResponse,
     convertStringArrayToBuffer,
+    createLuaLibWithLongRunningFunction,
+    encodableTransactionTest,
+    encodedTransactionTest,
     flushAndCloseClient,
     generateLuaLibCode,
     getClientConfigurationOption,
-    intoString,
     parseCommandLineArgs,
     parseEndpoints,
     transactionTest,
     validateTransactionResponse,
+    waitForNotBusy,
 } from "./TestUtilities";
 
 /* eslint-disable @typescript-eslint/no-var-requires */
@@ -119,13 +124,9 @@ describe("GlideClient", () => {
                 getClientConfigurationOption(cluster.getAddresses(), protocol),
             );
             const result = await client.info();
-            expect(intoString(result)).toEqual(
-                expect.stringContaining("# Server"),
-            );
-            expect(intoString(result)).toEqual(
-                expect.stringContaining("# Replication"),
-            );
-            expect(intoString(result)).toEqual(
+            expect(result).toEqual(expect.stringContaining("# Server"));
+            expect(result).toEqual(expect.stringContaining("# Replication"));
+            expect(result).toEqual(
                 expect.not.stringContaining("# Latencystats"),
             );
         },
@@ -142,21 +143,18 @@ describe("GlideClient", () => {
                 ),
             );
 
-            const blmovePromise = client.blmove(
-                "source",
-                "destination",
-                ListDirection.LEFT,
-                ListDirection.LEFT,
-                0.1,
-            );
-
-            const blmpopPromise = client.blmpop(
-                ["key1", "key2"],
-                ListDirection.LEFT,
-                0.1,
-            );
-
-            const promiseList = [blmovePromise, blmpopPromise];
+            const promiseList = [
+                client.blmove(
+                    "source",
+                    "destination",
+                    ListDirection.LEFT,
+                    ListDirection.LEFT,
+                    0.1,
+                ),
+                client.blmpop(["key1", "key2"], ListDirection.LEFT, 0.1),
+                client.bzpopmax(["key1", "key2"], 0),
+                client.bzpopmin(["key1", "key2"], 0),
+            ];
 
             try {
                 for (const promise of promiseList) {
@@ -203,6 +201,52 @@ describe("GlideClient", () => {
         },
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        "bytes decoder client test %p",
+        async (protocol) => {
+            const clientConfig = getClientConfigurationOption(
+                cluster.getAddresses(),
+                protocol,
+            );
+            clientConfig.defaultDecoder = Decoder.Bytes;
+            client = await GlideClient.createClient(clientConfig);
+            expect(await client.select(0)).toEqual("OK");
+
+            const key = uuidv4();
+            const value = uuidv4();
+            const valueEncoded = Buffer.from(value);
+            const result = await client.set(key, value);
+            expect(result).toEqual("OK");
+
+            expect(await client.get(key)).toEqual(valueEncoded);
+            expect(await client.get(key, Decoder.String)).toEqual(value);
+            expect(await client.get(key, Decoder.Bytes)).toEqual(valueEncoded);
+        },
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        "string decoder client test %p",
+        async (protocol) => {
+            const clientConfig = getClientConfigurationOption(
+                cluster.getAddresses(),
+                protocol,
+            );
+            clientConfig.defaultDecoder = Decoder.String;
+            client = await GlideClient.createClient(clientConfig);
+            expect(await client.select(0)).toEqual("OK");
+
+            const key = uuidv4();
+            const value = uuidv4();
+            const valueEncoded = Buffer.from(value);
+            const result = await client.set(key, value);
+            expect(result).toEqual("OK");
+
+            expect(await client.get(key)).toEqual(value);
+            expect(await client.get(key, Decoder.String)).toEqual(value);
+            expect(await client.get(key, Decoder.Bytes)).toEqual(valueEncoded);
+        },
+    );
+
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
         `can send transactions_%p`,
         async (protocol) => {
@@ -222,6 +266,69 @@ describe("GlideClient", () => {
         },
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `can get Bytes decoded transactions_%p`,
+        async (protocol) => {
+            client = await GlideClient.createClient(
+                getClientConfigurationOption(cluster.getAddresses(), protocol),
+            );
+            const transaction = new Transaction();
+            const expectedRes = await encodedTransactionTest(transaction);
+            transaction.select(0);
+            const result = await client.exec(transaction, Decoder.Bytes);
+            expectedRes.push(["select(0)", "OK"]);
+
+            validateTransactionResponse(result, expectedRes);
+        },
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `can send transaction with default string decoder_%p`,
+        async (protocol) => {
+            const clientConfig = getClientConfigurationOption(
+                cluster.getAddresses(),
+                protocol,
+            );
+            clientConfig.defaultDecoder = Decoder.String;
+            client = await GlideClient.createClient(clientConfig);
+            expect(await client.select(0)).toEqual("OK");
+            const transaction = new Transaction();
+            const expectedRes = await encodableTransactionTest(
+                transaction,
+                "value",
+            );
+            transaction.select(0);
+            const result = await client.exec(transaction);
+            expectedRes.push(["select(0)", "OK"]);
+
+            validateTransactionResponse(result, expectedRes);
+        },
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `can send transaction with default bytes decoder_%p`,
+        async (protocol) => {
+            const clientConfig = getClientConfigurationOption(
+                cluster.getAddresses(),
+                protocol,
+            );
+            clientConfig.defaultDecoder = Decoder.Bytes;
+            client = await GlideClient.createClient(clientConfig);
+            expect(await client.select(0)).toEqual("OK");
+            const transaction = new Transaction();
+            const valueEncoded = Buffer.from("value");
+            const expectedRes = await encodableTransactionTest(
+                transaction,
+                valueEncoded,
+            );
+            transaction.select(0);
+            const result = await client.exec(transaction);
+            expectedRes.push(["select(0)", "OK"]);
+
+            validateTransactionResponse(result, expectedRes);
+        },
+    );
+
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
         "can return null on WATCH transaction failures",
         async (protocol) => {
@@ -366,32 +473,22 @@ describe("GlideClient", () => {
             );
 
             const result = await client.lolwut();
-            expect(intoString(result)).toEqual(
-                expect.stringContaining("Redis ver. "),
-            );
+            expect(result).toEqual(expect.stringContaining("Redis ver. "));
 
             const result2 = await client.lolwut({ parameters: [] });
-            expect(intoString(result2)).toEqual(
-                expect.stringContaining("Redis ver. "),
-            );
+            expect(result2).toEqual(expect.stringContaining("Redis ver. "));
 
             const result3 = await client.lolwut({ parameters: [50, 20] });
-            expect(intoString(result3)).toEqual(
-                expect.stringContaining("Redis ver. "),
-            );
+            expect(result3).toEqual(expect.stringContaining("Redis ver. "));
 
             const result4 = await client.lolwut({ version: 6 });
-            expect(intoString(result4)).toEqual(
-                expect.stringContaining("Redis ver. "),
-            );
+            expect(result4).toEqual(expect.stringContaining("Redis ver. "));
 
             const result5 = await client.lolwut({
                 version: 5,
                 parameters: [30, 4, 4],
             });
-            expect(intoString(result5)).toEqual(
-                expect.stringContaining("Redis ver. "),
-            );
+            expect(result5).toEqual(expect.stringContaining("Redis ver. "));
 
             // transaction tests
             const transaction = new Transaction();
@@ -403,7 +500,7 @@ describe("GlideClient", () => {
 
             if (results) {
                 for (const element of results) {
-                    expect(intoString(element)).toEqual(
+                    expect(element).toEqual(
                         expect.stringContaining("Redis ver. "),
                     );
                 }
@@ -507,7 +604,47 @@ describe("GlideClient", () => {
     );
 
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
-        "function load test_%p",
+        "move test_%p",
+        async (protocol) => {
+            const client = await GlideClient.createClient(
+                getClientConfigurationOption(cluster.getAddresses(), protocol),
+            );
+
+            const key1 = "{key}-1" + uuidv4();
+            const key2 = "{key}-2" + uuidv4();
+            const value = uuidv4();
+
+            expect(await client.select(0)).toEqual("OK");
+            expect(await client.move(key1, 1)).toEqual(false);
+
+            expect(await client.set(key1, value)).toEqual("OK");
+            expect(await client.get(key1)).toEqual(value);
+            expect(await client.move(key1, 1)).toEqual(true);
+            expect(await client.get(key1)).toEqual(null);
+            expect(await client.select(1)).toEqual("OK");
+            expect(await client.get(key1)).toEqual(value);
+
+            await expect(client.move(key1, -1)).rejects.toThrow(RequestError);
+
+            //transaction tests
+            const transaction = new Transaction();
+            transaction.select(1);
+            transaction.move(key2, 0);
+            transaction.set(key2, value);
+            transaction.move(key2, 0);
+            transaction.select(0);
+            transaction.get(key2);
+            const results = await client.exec(transaction);
+
+            expect(results).toEqual(["OK", false, "OK", true, "OK", value]);
+
+            client.close();
+        },
+        TIMEOUT,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        "function load function list function stats test_%p",
         async (protocol) => {
             if (cluster.checkIfServerVersionLessThan("7.0.0")) return;
 
@@ -534,6 +671,9 @@ describe("GlideClient", () => {
                     await client.fcallReadonly(funcName, [], ["one", "two"]),
                 ).toEqual("one");
 
+                let functionStats = await client.functionStats();
+                checkFunctionStatsResponse(functionStats, [], 1, 1);
+
                 let functionList = await client.functionList({
                     libNamePattern: libName,
                 });
@@ -592,6 +732,9 @@ describe("GlideClient", () => {
                     newCode,
                 );
 
+                functionStats = await client.functionStats();
+                checkFunctionStatsResponse(functionStats, [], 1, 2);
+
                 expect(
                     await client.fcall(func2Name, [], ["one", "two"]),
                 ).toEqual(2);
@@ -600,6 +743,8 @@ describe("GlideClient", () => {
                 ).toEqual(2);
             } finally {
                 expect(await client.functionFlush()).toEqual("OK");
+                const functionStats = await client.functionStats();
+                checkFunctionStatsResponse(functionStats, [], 0, 0);
                 client.close();
             }
         },
@@ -687,6 +832,155 @@ describe("GlideClient", () => {
         },
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        "function kill RO func %p",
+        async (protocol) => {
+            if (cluster.checkIfServerVersionLessThan("7.0.0")) return;
+
+            const config = getClientConfigurationOption(
+                cluster.getAddresses(),
+                protocol,
+                10000,
+            );
+            const client = await GlideClient.createClient(config);
+            const testClient = await GlideClient.createClient(config);
+
+            try {
+                const libName = "function_kill_no_write";
+                const funcName = "deadlock_no_write";
+                const code = createLuaLibWithLongRunningFunction(
+                    libName,
+                    funcName,
+                    6,
+                    true,
+                );
+                expect(await client.functionFlush()).toEqual("OK");
+                // nothing to kill
+                await expect(client.functionKill()).rejects.toThrow(/notbusy/i);
+
+                // load the lib
+                expect(await client.functionLoad(code, true)).toEqual(libName);
+
+                try {
+                    // call the function without await
+                    const promise = testClient
+                        .fcall(funcName, [], [])
+                        .catch((e) =>
+                            expect((e as Error).message).toContain(
+                                "Script killed",
+                            ),
+                        );
+
+                    let killed = false;
+                    let timeout = 4000;
+                    await new Promise((resolve) => setTimeout(resolve, 1000));
+
+                    while (timeout >= 0) {
+                        try {
+                            expect(await client.functionKill()).toEqual("OK");
+                            killed = true;
+                            break;
+                        } catch {
+                            // do nothing
+                        }
+
+                        await new Promise((resolve) =>
+                            setTimeout(resolve, 500),
+                        );
+                        timeout -= 500;
+                    }
+
+                    expect(killed).toBeTruthy();
+                    await promise;
+                } finally {
+                    await waitForNotBusy(client);
+                }
+            } finally {
+                expect(await client.functionFlush()).toEqual("OK");
+                testClient.close();
+                client.close();
+            }
+        },
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        "function kill RW func %p",
+        async (protocol) => {
+            if (cluster.checkIfServerVersionLessThan("7.0.0")) return;
+
+            const config = getClientConfigurationOption(
+                cluster.getAddresses(),
+                protocol,
+                10000,
+            );
+            const client = await GlideClient.createClient(config);
+            const testClient = await GlideClient.createClient(config);
+
+            try {
+                const libName = "function_kill_write";
+                const key = libName;
+                const funcName = "deadlock_write";
+                const code = createLuaLibWithLongRunningFunction(
+                    libName,
+                    funcName,
+                    6,
+                    false,
+                );
+                expect(await client.functionFlush()).toEqual("OK");
+                // nothing to kill
+                await expect(client.functionKill()).rejects.toThrow(/notbusy/i);
+
+                // load the lib
+                expect(await client.functionLoad(code, true)).toEqual(libName);
+
+                let promise = null;
+
+                try {
+                    // call the function without await
+                    promise = testClient.fcall(funcName, [key], []);
+
+                    let foundUnkillable = false;
+                    let timeout = 4000;
+                    await new Promise((resolve) => setTimeout(resolve, 1000));
+
+                    while (timeout >= 0) {
+                        try {
+                            // valkey kills a function with 5 sec delay
+                            // but this will always throw an error in the test
+                            await client.functionKill();
+                        } catch (err) {
+                            // looking for an error with "unkillable" in the message
+                            // at that point we can break the loop
+                            if (
+                                (err as Error).message
+                                    .toLowerCase()
+                                    .includes("unkillable")
+                            ) {
+                                foundUnkillable = true;
+                                break;
+                            }
+                        }
+
+                        await new Promise((resolve) =>
+                            setTimeout(resolve, 500),
+                        );
+                        timeout -= 500;
+                    }
+
+                    expect(foundUnkillable).toBeTruthy();
+                } finally {
+                    // If function wasn't killed, and it didn't time out - it blocks the server and cause rest
+                    // test to fail. Wait for the function to complete (we cannot kill it)
+                    expect(await promise).toContain("Timed out");
+                }
+            } finally {
+                expect(await client.functionFlush()).toEqual("OK");
+                testClient.close();
+                client.close();
+            }
+        },
+    );
+
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
         "sort sortstore sort_store sortro sort_ro sortreadonly test_%p",
         async (protocol) => {
@@ -1047,6 +1341,61 @@ describe("GlideClient", () => {
         TIMEOUT,
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        "xinfo stream transaction test_%p",
+        async (protocol) => {
+            const client = await GlideClient.createClient(
+                getClientConfigurationOption(cluster.getAddresses(), protocol),
+            );
+
+            const key = uuidv4();
+
+            const transaction = new Transaction();
+            transaction.xadd(key, [["field1", "value1"]], { id: "0-1" });
+            transaction.xinfoStream(key);
+            transaction.xinfoStream(key, true);
+            const result = await client.exec(transaction);
+            expect(result).not.toBeNull();
+
+            const versionLessThan7 =
+                cluster.checkIfServerVersionLessThan("7.0.0");
+
+            const expectedXinfoStreamResult = {
+                length: 1,
+                "radix-tree-keys": 1,
+                "radix-tree-nodes": 2,
+                "last-generated-id": "0-1",
+                groups: 0,
+                "first-entry": ["0-1", ["field1", "value1"]],
+                "last-entry": ["0-1", ["field1", "value1"]],
+                "max-deleted-entry-id": versionLessThan7 ? undefined : "0-0",
+                "entries-added": versionLessThan7 ? undefined : 1,
+                "recorded-first-entry-id": versionLessThan7 ? undefined : "0-1",
+            };
+
+            const expectedXinfoStreamFullResult = {
+                length: 1,
+                "radix-tree-keys": 1,
+                "radix-tree-nodes": 2,
+                "last-generated-id": "0-1",
+                entries: [["0-1", ["field1", "value1"]]],
+                groups: [],
+                "max-deleted-entry-id": versionLessThan7 ? undefined : "0-0",
+                "entries-added": versionLessThan7 ? undefined : 1,
+                "recorded-first-entry-id": versionLessThan7 ? undefined : "0-1",
+            };
+
+            if (result != null) {
+                expect(result[0]).toEqual("0-1"); // xadd
+                expect(result[1]).toEqual(expectedXinfoStreamResult);
+                expect(result[2]).toEqual(expectedXinfoStreamFullResult);
+            }
+
+            client.close();
+        },
+        TIMEOUT,
+    );
+
     runBaseTests({
         init: async (protocol, clientName?) => {
             const options = getClientConfigurationOption(
diff --git a/node/tests/GlideClientInternals.test.ts b/node/tests/GlideClientInternals.test.ts
index ed5fc35d92..72ee0fe8f1 100644
--- a/node/tests/GlideClientInternals.test.ts
+++ b/node/tests/GlideClientInternals.test.ts
@@ -22,6 +22,7 @@ import {
     BaseClientConfiguration,
     ClosingError,
     ClusterTransaction,
+    Decoder,
     GlideClient,
     GlideClientConfiguration,
     GlideClusterClient,
@@ -31,13 +32,14 @@ import {
     RequestError,
     ReturnType,
     SlotKeyTypes,
+    TimeUnit,
 } from "..";
 import {
     command_request,
     connection_request,
     response,
 } from "../src/ProtobufMessage";
-import { convertStringArrayToBuffer, intoString } from "./TestUtilities";
+import { convertStringArrayToBuffer } from "./TestUtilities";
 const { RequestType, CommandRequest } = command_request;
 
 beforeAll(() => {
@@ -308,8 +310,9 @@ describe("SocketConnectionInternals", () => {
                         },
                     );
                 });
-                const result = await connection.get("foo");
-                expect(intoString(result)).toEqual(intoString(expected));
+                const result = await connection.get("foo", Decoder.String);
+                console.log(result);
+                expect(result).toEqual(expected);
             });
         };
 
@@ -384,7 +387,9 @@ describe("SocketConnectionInternals", () => {
                 type: "primarySlotKey",
                 key: "key",
             };
-            const result = await connection.exec(transaction, slotKey);
+            const result = await connection.exec(transaction, {
+                route: slotKey,
+            });
             expect(result).toBe("OK");
         });
     });
@@ -412,10 +417,10 @@ describe("SocketConnectionInternals", () => {
             });
             const transaction = new ClusterTransaction();
             transaction.info([InfoOptions.Server]);
-            const result = await connection.exec(transaction, "randomNode");
-            expect(intoString(result)).toEqual(
-                expect.stringContaining("# Server"),
-            );
+            const result = await connection.exec(transaction, {
+                route: "randomNode",
+            });
+            expect(result).toEqual(expect.stringContaining("# Server"));
         });
     });
 
@@ -541,7 +546,7 @@ describe("SocketConnectionInternals", () => {
             const request1 = connection.set("foo", "bar", {
                 conditionalSet: "onlyIfExists",
                 returnOldValue: true,
-                expiry: { type: "seconds", count: 10 },
+                expiry: { type: TimeUnit.Seconds, count: 10 },
             });
 
             expect(await request1).toMatch("OK");
@@ -701,14 +706,13 @@ describe("SocketConnectionInternals", () => {
             });
             const result1 = await connection.customCommand(
                 ["SET", "foo", "bar"],
-                route1,
+                { route: route1 },
             );
             expect(result1).toBeNull();
 
-            const result2 = await connection.customCommand(
-                ["GET", "foo"],
-                route2,
-            );
+            const result2 = await connection.customCommand(["GET", "foo"], {
+                route: route2,
+            });
             expect(result2).toBeNull();
         });
     });
diff --git a/node/tests/GlideClusterClient.test.ts b/node/tests/GlideClusterClient.test.ts
index 930df79c62..4304a5a988 100644
--- a/node/tests/GlideClusterClient.test.ts
+++ b/node/tests/GlideClusterClient.test.ts
@@ -15,6 +15,7 @@ import { v4 as uuidv4 } from "uuid";
 import {
     BitwiseOperation,
     ClusterTransaction,
+    Decoder,
     FunctionListResponse,
     GlideClusterClient,
     InfoOptions,
@@ -25,11 +26,18 @@ import {
     ScoreFilter,
 } from "..";
 import { RedisCluster } from "../../utils/TestUtils.js";
-import { FlushMode, SortOrder } from "../build-ts/src/Commands";
+import {
+    FlushMode,
+    FunctionStatsResponse,
+    GeoUnit,
+    SortOrder,
+} from "../build-ts/src/Commands";
 import { runBaseTests } from "./SharedTests";
 import {
     checkClusterResponse,
     checkFunctionListResponse,
+    checkFunctionStatsResponse,
+    createLuaLibWithLongRunningFunction,
     flushAndCloseClient,
     generateLuaLibCode,
     getClientConfigurationOption,
@@ -40,6 +48,7 @@ import {
     parseEndpoints,
     transactionTest,
     validateTransactionResponse,
+    waitForNotBusy,
 } from "./TestUtilities";
 type Context = {
     client: GlideClusterClient;
@@ -107,9 +116,7 @@ describe("GlideClusterClient", () => {
             const info_server = getFirstResult(
                 await client.info([InfoOptions.Server]),
             );
-            expect(intoString(info_server)).toEqual(
-                expect.stringContaining("# Server"),
-            );
+            expect(info_server).toEqual(expect.stringContaining("# Server"));
 
             const infoReplicationValues = Object.values(
                 await client.info([InfoOptions.Replication]),
@@ -135,12 +142,8 @@ describe("GlideClusterClient", () => {
                 [InfoOptions.Server],
                 "randomNode",
             );
-            expect(intoString(result)).toEqual(
-                expect.stringContaining("# Server"),
-            );
-            expect(intoString(result)).toEqual(
-                expect.not.stringContaining("# Errorstats"),
-            );
+            expect(result).toEqual(expect.stringContaining("# Server"));
+            expect(result).toEqual(expect.not.stringContaining("# Errorstats"));
         },
         TIMEOUT,
     );
@@ -163,10 +166,9 @@ describe("GlideClusterClient", () => {
             );
             const result = cleanResult(
                 intoString(
-                    await client.customCommand(
-                        ["cluster", "nodes"],
-                        "randomNode",
-                    ),
+                    await client.customCommand(["cluster", "nodes"], {
+                        route: "randomNode",
+                    }),
                 ),
             );
 
@@ -180,8 +182,10 @@ describe("GlideClusterClient", () => {
             const secondResult = cleanResult(
                 intoString(
                     await client.customCommand(["cluster", "nodes"], {
-                        type: "routeByAddress",
-                        host,
+                        route: {
+                            type: "routeByAddress",
+                            host,
+                        },
                     }),
                 ),
             );
@@ -194,9 +198,11 @@ describe("GlideClusterClient", () => {
             const thirdResult = cleanResult(
                 intoString(
                     await client.customCommand(["cluster", "nodes"], {
-                        type: "routeByAddress",
-                        host: host2,
-                        port: Number(port),
+                        route: {
+                            type: "routeByAddress",
+                            host: host2,
+                            port: Number(port),
+                        },
                     }),
                 ),
             );
@@ -212,12 +218,50 @@ describe("GlideClusterClient", () => {
             client = await GlideClusterClient.createClient(
                 getClientConfigurationOption(cluster.getAddresses(), protocol),
             );
-            expect(() =>
+            await expect(
                 client.info(undefined, {
                     type: "routeByAddress",
                     host: "foo",
                 }),
-            ).toThrowError();
+            ).rejects.toThrowError(RequestError);
+        },
+        TIMEOUT,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `dump and restore custom command_%p`,
+        async (protocol) => {
+            client = await GlideClusterClient.createClient(
+                getClientConfigurationOption(cluster.getAddresses(), protocol),
+            );
+
+            const key = "key";
+            const value = "value";
+            const valueEncoded = Buffer.from(value);
+            expect(await client.set(key, value)).toEqual("OK");
+            // Since DUMP gets binary results, we cannot use the default decoder (string) here, so we expected to get an error.
+            // TODO: fix custom command with unmatch decoder to return an error: https://github.com/valkey-io/valkey-glide/issues/2119
+            // expect(await client.customCommand(["DUMP", key])).toThrowError();
+            const dumpResult = await client.customCommand(["DUMP", key], {
+                decoder: Decoder.Bytes,
+            });
+            expect(await client.del([key])).toEqual(1);
+
+            if (dumpResult instanceof Buffer) {
+                // check the delete
+                expect(await client.get(key)).toEqual(null);
+                expect(
+                    await client.customCommand(
+                        ["RESTORE", key, "0", dumpResult],
+                        { decoder: Decoder.Bytes },
+                    ),
+                ).toEqual("OK");
+                // check the restore
+                expect(await client.get(key)).toEqual(value);
+                expect(await client.get(key, Decoder.Bytes)).toEqual(
+                    valueEncoded,
+                );
+            }
         },
         TIMEOUT,
     );
@@ -339,6 +383,8 @@ describe("GlideClusterClient", () => {
                 client.sortStore("abc", "zyx", { isAlpha: true }),
                 client.lmpop(["abc", "def"], ListDirection.LEFT, 1),
                 client.blmpop(["abc", "def"], ListDirection.RIGHT, 0.1, 1),
+                client.bzpopmax(["abc", "def"], 0.5),
+                client.bzpopmin(["abc", "def"], 0.5),
             ];
 
             if (gte(cluster.getVersion(), "6.2.0")) {
@@ -354,6 +400,13 @@ describe("GlideClusterClient", () => {
                     client.zdiffWithScores(["abc", "zxy", "lkn"]),
                     client.zdiffstore("abc", ["zxy", "lkn"]),
                     client.copy("abc", "zxy", true),
+                    client.geosearchstore(
+                        "abc",
+                        "zxy",
+                        { member: "_" },
+                        { radius: 5, unit: GeoUnit.METERS },
+                    ),
+                    client.zrangeStore("abc", "zyx", { start: 0, stop: -1 }),
                 );
             }
 
@@ -733,7 +786,7 @@ describe("GlideClusterClient", () => {
                 "Single node route = %s",
                 (singleNodeRoute) => {
                     it(
-                        "function load and function list",
+                        "function load function list function stats",
                         async () => {
                             if (cluster.checkIfServerVersionLessThan("7.0.0"))
                                 return;
@@ -769,6 +822,21 @@ describe("GlideClusterClient", () => {
                                     singleNodeRoute,
                                     (value) => expect(value).toEqual([]),
                                 );
+
+                                let functionStats =
+                                    await client.functionStats(route);
+                                checkClusterResponse(
+                                    functionStats as object,
+                                    singleNodeRoute,
+                                    (value) =>
+                                        checkFunctionStatsResponse(
+                                            value as FunctionStatsResponse,
+                                            [],
+                                            0,
+                                            0,
+                                        ),
+                                );
+
                                 // load the library
                                 expect(await client.functionLoad(code)).toEqual(
                                     libName,
@@ -797,6 +865,19 @@ describe("GlideClusterClient", () => {
                                             expectedFlags,
                                         ),
                                 );
+                                functionStats =
+                                    await client.functionStats(route);
+                                checkClusterResponse(
+                                    functionStats as object,
+                                    singleNodeRoute,
+                                    (value) =>
+                                        checkFunctionStatsResponse(
+                                            value as FunctionStatsResponse,
+                                            [],
+                                            1,
+                                            1,
+                                        ),
+                                );
 
                                 // call functions from that library to confirm that it works
                                 let fcall = await client.fcallWithRoute(
@@ -875,6 +956,19 @@ describe("GlideClusterClient", () => {
                                             newCode,
                                         ),
                                 );
+                                functionStats =
+                                    await client.functionStats(route);
+                                checkClusterResponse(
+                                    functionStats as object,
+                                    singleNodeRoute,
+                                    (value) =>
+                                        checkFunctionStatsResponse(
+                                            value as FunctionStatsResponse,
+                                            [],
+                                            1,
+                                            2,
+                                        ),
+                                );
 
                                 fcall = await client.fcallWithRoute(
                                     func2Name,
@@ -906,17 +1000,6 @@ describe("GlideClusterClient", () => {
                         },
                         TIMEOUT,
                     );
-                },
-            );
-        },
-    );
-
-    describe.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
-        "Protocol is RESP2 = %s",
-        (protocol) => {
-            describe.each([true, false])(
-                "Single node route = %s",
-                (singleNodeRoute) => {
                     it(
                         "function flush",
                         async () => {
@@ -1003,17 +1086,6 @@ describe("GlideClusterClient", () => {
                         },
                         TIMEOUT,
                     );
-                },
-            );
-        },
-    );
-
-    describe.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
-        "Protocol is RESP2 = %s",
-        (protocol) => {
-            describe.each([true, false])(
-                "Single node route = %s",
-                (singleNodeRoute) => {
                     it(
                         "function delete",
                         async () => {
@@ -1087,7 +1159,201 @@ describe("GlideClusterClient", () => {
                         },
                         TIMEOUT,
                     );
+                    it(
+                        "function kill with route",
+                        async () => {
+                            if (cluster.checkIfServerVersionLessThan("7.0.0"))
+                                return;
+
+                            const config = getClientConfigurationOption(
+                                cluster.getAddresses(),
+                                protocol,
+                                10000,
+                            );
+                            const client =
+                                await GlideClusterClient.createClient(config);
+                            const testClient =
+                                await GlideClusterClient.createClient(config);
+
+                            try {
+                                const libName =
+                                    "function_kill_no_write_with_route_" +
+                                    singleNodeRoute;
+                                const funcName =
+                                    "deadlock_with_route_" + singleNodeRoute;
+                                const code =
+                                    createLuaLibWithLongRunningFunction(
+                                        libName,
+                                        funcName,
+                                        6,
+                                        true,
+                                    );
+                                const route: Routes = singleNodeRoute
+                                    ? { type: "primarySlotKey", key: "1" }
+                                    : "allPrimaries";
+                                expect(await client.functionFlush()).toEqual(
+                                    "OK",
+                                );
+
+                                // nothing to kill
+                                await expect(
+                                    client.functionKill(route),
+                                ).rejects.toThrow(/notbusy/i);
+
+                                // load the lib
+                                expect(
+                                    await client.functionLoad(
+                                        code,
+                                        true,
+                                        route,
+                                    ),
+                                ).toEqual(libName);
+
+                                try {
+                                    // call the function without await
+                                    const promise = testClient
+                                        .fcallWithRoute(funcName, [], route)
+                                        .catch((e) =>
+                                            expect(
+                                                (e as Error).message,
+                                            ).toContain("Script killed"),
+                                        );
+
+                                    let killed = false;
+                                    let timeout = 4000;
+                                    await new Promise((resolve) =>
+                                        setTimeout(resolve, 1000),
+                                    );
+
+                                    while (timeout >= 0) {
+                                        try {
+                                            expect(
+                                                await client.functionKill(
+                                                    route,
+                                                ),
+                                            ).toEqual("OK");
+                                            killed = true;
+                                            break;
+                                        } catch {
+                                            // do nothing
+                                        }
+
+                                        await new Promise((resolve) =>
+                                            setTimeout(resolve, 500),
+                                        );
+                                        timeout -= 500;
+                                    }
+
+                                    expect(killed).toBeTruthy();
+                                    await promise;
+                                } finally {
+                                    await waitForNotBusy(client);
+                                }
+                            } finally {
+                                expect(await client.functionFlush()).toEqual(
+                                    "OK",
+                                );
+                                client.close();
+                                testClient.close();
+                            }
+                        },
+                        TIMEOUT,
+                    );
+                },
+            );
+            it(
+                "function kill key based write function",
+                async () => {
+                    if (cluster.checkIfServerVersionLessThan("7.0.0")) return;
+
+                    const config = getClientConfigurationOption(
+                        cluster.getAddresses(),
+                        protocol,
+                        10000,
+                    );
+                    const client =
+                        await GlideClusterClient.createClient(config);
+                    const testClient =
+                        await GlideClusterClient.createClient(config);
+
+                    try {
+                        const libName =
+                            "function_kill_key_based_write_function";
+                        const funcName =
+                            "deadlock_write_function_with_key_based_route";
+                        const key = libName;
+                        const code = createLuaLibWithLongRunningFunction(
+                            libName,
+                            funcName,
+                            6,
+                            false,
+                        );
+
+                        const route: Routes = {
+                            type: "primarySlotKey",
+                            key: key,
+                        };
+                        expect(await client.functionFlush()).toEqual("OK");
+
+                        // nothing to kill
+                        await expect(
+                            client.functionKill(route),
+                        ).rejects.toThrow(/notbusy/i);
+
+                        // load the lib
+                        expect(
+                            await client.functionLoad(code, true, route),
+                        ).toEqual(libName);
+
+                        let promise = null;
+
+                        try {
+                            // call the function without await
+                            promise = testClient.fcall(funcName, [key], []);
+
+                            let foundUnkillable = false;
+                            let timeout = 4000;
+                            await new Promise((resolve) =>
+                                setTimeout(resolve, 1000),
+                            );
+
+                            while (timeout >= 0) {
+                                try {
+                                    // valkey kills a function with 5 sec delay
+                                    // but this will always throw an error in the test
+                                    await client.functionKill(route);
+                                } catch (err) {
+                                    // looking for an error with "unkillable" in the message
+                                    // at that point we can break the loop
+                                    if (
+                                        (err as Error).message
+                                            .toLowerCase()
+                                            .includes("unkillable")
+                                    ) {
+                                        foundUnkillable = true;
+                                        break;
+                                    }
+                                }
+
+                                await new Promise((resolve) =>
+                                    setTimeout(resolve, 500),
+                                );
+                                timeout -= 500;
+                            }
+
+                            expect(foundUnkillable).toBeTruthy();
+                        } finally {
+                            // If function wasn't killed, and it didn't time out - it blocks the server and cause rest
+                            // test to fail. Wait for the function to complete (we cannot kill it)
+                            expect(await promise).toContain("Timed out");
+                        }
+                    } finally {
+                        expect(await client.functionFlush()).toEqual("OK");
+                        client.close();
+                        testClient.close();
+                    }
                 },
+                TIMEOUT,
             );
         },
     );
diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts
index b045b866c4..8dea7c45d1 100644
--- a/node/tests/SharedTests.ts
+++ b/node/tests/SharedTests.ts
@@ -22,13 +22,14 @@ import {
     ClosingError,
     ClusterTransaction,
     ConditionalChange,
+    Decoder,
     ExpireOptions,
     FlushMode,
     GeoUnit,
     GeospatialData,
     GlideClient,
     GlideClusterClient,
-    InfScoreBoundary,
+    InfBoundary,
     InfoOptions,
     InsertPosition,
     ListDirection,
@@ -38,6 +39,7 @@ import {
     Script,
     SignedEncoding,
     SortOrder,
+    TimeUnit,
     Transaction,
     UnsignedEncoding,
     UpdateByScore,
@@ -347,10 +349,21 @@ export function runBaseTests(config: {
                     [key2]: value,
                     [key3]: value,
                 };
+                const valueEncoded = Buffer.from(value);
+
                 expect(await client.mset(keyValueList)).toEqual("OK");
                 expect(
                     await client.mget([key1, key2, "nonExistingKey", key3]),
                 ).toEqual([value, value, null, value]);
+
+                //mget with binary buffers
+                expect(await client.mset(keyValueList)).toEqual("OK");
+                expect(
+                    await client.mget(
+                        [key1, key2, "nonExistingKey", key3],
+                        Decoder.Bytes,
+                    ),
+                ).toEqual([valueEncoded, valueEncoded, null, valueEncoded]);
             }, protocol);
         },
         config.timeout,
@@ -470,8 +483,21 @@ export function runBaseTests(config: {
         `ping test_%p`,
         async (protocol) => {
             await runTest(async (client: BaseClient) => {
+                const pongEncoded = Buffer.from("PONG");
+                const helloEncoded = Buffer.from("Hello");
                 expect(await client.ping()).toEqual("PONG");
-                expect(await client.ping("Hello")).toEqual("Hello");
+                expect(await client.ping({ message: "Hello" })).toEqual(
+                    "Hello",
+                );
+                expect(await client.ping({ decoder: Decoder.Bytes })).toEqual(
+                    pongEncoded,
+                );
+                expect(
+                    await client.ping({
+                        message: "Hello",
+                        decoder: Decoder.Bytes,
+                    }),
+                ).toEqual(helloEncoded);
             }, protocol);
         },
         config.timeout,
@@ -1175,12 +1201,19 @@ export function runBaseTests(config: {
             await runTest(async (client: BaseClient) => {
                 const key1 = uuidv4();
                 const value1 = uuidv4();
+                const value1Encoded = Buffer.from(value1);
                 const key2 = uuidv4();
 
                 expect(await client.set(key1, value1)).toEqual("OK");
                 expect(await client.getdel(key1)).toEqual(value1);
                 expect(await client.getdel(key1)).toEqual(null);
 
+                expect(await client.set(key1, value1)).toEqual("OK");
+                expect(await client.getdel(key1, Decoder.Bytes)).toEqual(
+                    value1Encoded,
+                );
+                expect(await client.getdel(key1, Decoder.Bytes)).toEqual(null);
+
                 // key isn't a string
                 expect(await client.sadd(key2, ["a"])).toEqual(1);
                 await expect(client.getdel(key2)).rejects.toThrow(RequestError);
@@ -1195,6 +1228,7 @@ export function runBaseTests(config: {
             await runTest(async (client: BaseClient, cluster) => {
                 const key = uuidv4();
                 const nonStringKey = uuidv4();
+                const valueEncoded = Buffer.from("This is a string");
 
                 expect(await client.set(key, "This is a string")).toEqual("OK");
                 expect(await client.getrange(key, 0, 3)).toEqual("This");
@@ -1203,6 +1237,18 @@ export function runBaseTests(config: {
                     "This is a string",
                 );
 
+                // range of binary buffer
+                expect(await client.set(key, "This is a string")).toEqual("OK");
+                expect(await client.getrange(key, 0, 3, Decoder.Bytes)).toEqual(
+                    valueEncoded.subarray(0, 4),
+                );
+                expect(
+                    await client.getrange(key, -3, -1, Decoder.Bytes),
+                ).toEqual(valueEncoded.subarray(-3, valueEncoded.length));
+                expect(
+                    await client.getrange(key, 0, -1, Decoder.Bytes),
+                ).toEqual(valueEncoded.subarray(0, valueEncoded.length));
+
                 // out of range
                 expect(await client.getrange(key, 10, 100)).toEqual("string");
                 expect(await client.getrange(key, -200, -3)).toEqual(
@@ -1245,12 +1291,303 @@ export function runBaseTests(config: {
                     [field1]: value,
                     [field2]: value,
                 };
+                const valueEncoded = Buffer.from(value);
+
                 expect(await client.hset(key, fieldValueMap)).toEqual(2);
                 expect(await client.hget(key, field1)).toEqual(value);
                 expect(await client.hget(key, field2)).toEqual(value);
                 expect(await client.hget(key, "nonExistingField")).toEqual(
                     null,
                 );
+
+                //hget with binary buffer
+                expect(await client.hget(key, field1, Decoder.Bytes)).toEqual(
+                    valueEncoded,
+                );
+                expect(await client.hget(key, field2, Decoder.Bytes)).toEqual(
+                    valueEncoded,
+                );
+                expect(
+                    await client.hget(key, "nonExistingField", Decoder.Bytes),
+                ).toEqual(null);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `testing hkeys with exiting, an non exising key and error request key_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key = uuidv4();
+                const key2 = uuidv4();
+                const field1 = uuidv4();
+                const field2 = uuidv4();
+                const value = uuidv4();
+                const value2 = uuidv4();
+                const fieldValueMap = {
+                    [field1]: value,
+                    [field2]: value2,
+                };
+
+                // set up hash with two keys/values
+                expect(await client.hset(key, fieldValueMap)).toEqual(2);
+                expect(await client.hkeys(key)).toEqual([field1, field2]);
+
+                // remove one key
+                expect(await client.hdel(key, [field1])).toEqual(1);
+                expect(await client.hkeys(key)).toEqual([field2]);
+
+                // non-existing key returns an empty list
+                expect(await client.hkeys("nonExistingKey")).toEqual([]);
+
+                // Key exists, but it is not a hash
+                expect(await client.set(key2, value)).toEqual("OK");
+                await expect(client.hkeys(key2)).rejects.toThrow();
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `hscan test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key1 = "{key}-1" + uuidv4();
+                const initialCursor = "0";
+                const defaultCount = 20;
+                const resultCursorIndex = 0;
+                const resultCollectionIndex = 1;
+
+                // Setup test data - use a large number of entries to force an iterative cursor.
+                const numberMap: Record = {};
+
+                for (let i = 0; i < 50000; i++) {
+                    numberMap[i.toString()] = "num" + i;
+                }
+
+                const charMembers = ["a", "b", "c", "d", "e"];
+                const charMap: Record = {};
+
+                for (let i = 0; i < charMembers.length; i++) {
+                    charMap[charMembers[i]] = i.toString();
+                }
+
+                // Result contains the whole set
+                expect(await client.hset(key1, charMap)).toEqual(
+                    charMembers.length,
+                );
+                let result = await client.hscan(key1, initialCursor);
+                expect(result[resultCursorIndex]).toEqual(initialCursor);
+                expect(result[resultCollectionIndex].length).toEqual(
+                    Object.keys(charMap).length * 2, // Length includes the score which is twice the map size
+                );
+
+                const resultArray = result[resultCollectionIndex];
+                const resultKeys = [];
+                const resultValues: string[] = [];
+
+                for (let i = 0; i < resultArray.length; i += 2) {
+                    resultKeys.push(resultArray[i]);
+                    resultValues.push(resultArray[i + 1]);
+                }
+
+                // Verify if all keys from charMap are in resultKeys
+                const allKeysIncluded = resultKeys.every(
+                    (key) => key in charMap,
+                );
+                expect(allKeysIncluded).toEqual(true);
+
+                const allValuesIncluded = Object.values(charMap).every(
+                    (value) => value in resultValues,
+                );
+                expect(allValuesIncluded).toEqual(true);
+
+                // Test hscan with match
+                result = await client.hscan(key1, initialCursor, {
+                    match: "a",
+                });
+
+                expect(result[resultCursorIndex]).toEqual(initialCursor);
+                expect(result[resultCollectionIndex]).toEqual(["a", "0"]);
+
+                // Set up testing data with the numberMap set to be used for the next set test keys and test results.
+                expect(await client.hset(key1, numberMap)).toEqual(
+                    Object.keys(numberMap).length,
+                );
+
+                let resultCursor = initialCursor;
+                const secondResultAllKeys: string[] = [];
+                const secondResultAllValues: string[] = [];
+                let isFirstLoop = true;
+
+                do {
+                    result = await client.hscan(key1, resultCursor);
+                    resultCursor = result[resultCursorIndex].toString();
+                    const resultEntry = result[resultCollectionIndex];
+
+                    for (let i = 0; i < resultEntry.length; i += 2) {
+                        secondResultAllKeys.push(resultEntry[i]);
+                        secondResultAllValues.push(resultEntry[i + 1]);
+                    }
+
+                    if (isFirstLoop) {
+                        expect(resultCursor).not.toBe("0");
+                        isFirstLoop = false;
+                    } else if (resultCursor === initialCursor) {
+                        break;
+                    }
+
+                    // Scan with result cursor has a different set
+                    const secondResult = await client.hscan(key1, resultCursor);
+                    const newResultCursor =
+                        secondResult[resultCursorIndex].toString();
+                    expect(resultCursor).not.toBe(newResultCursor);
+                    resultCursor = newResultCursor;
+                    const secondResultEntry =
+                        secondResult[resultCollectionIndex];
+
+                    expect(result[resultCollectionIndex]).not.toBe(
+                        secondResult[resultCollectionIndex],
+                    );
+
+                    for (let i = 0; i < secondResultEntry.length; i += 2) {
+                        secondResultAllKeys.push(secondResultEntry[i]);
+                        secondResultAllValues.push(secondResultEntry[i + 1]);
+                    }
+                } while (resultCursor != initialCursor); // 0 is returned for the cursor of the last iteration.
+
+                // Verify all data is found in hscan
+                const allSecondResultKeys = Object.keys(numberMap).every(
+                    (key) => key in secondResultAllKeys,
+                );
+                expect(allSecondResultKeys).toEqual(true);
+
+                const allSecondResultValues = Object.keys(numberMap).every(
+                    (value) => value in secondResultAllValues,
+                );
+                expect(allSecondResultValues).toEqual(true);
+
+                // Test match pattern
+                result = await client.hscan(key1, initialCursor, {
+                    match: "*",
+                });
+                expect(result[resultCursorIndex]).not.toEqual(initialCursor);
+                expect(
+                    result[resultCollectionIndex].length,
+                ).toBeGreaterThanOrEqual(defaultCount);
+
+                // Test count
+                result = await client.hscan(key1, initialCursor, {
+                    count: 25,
+                });
+                expect(result[resultCursorIndex]).not.toEqual(initialCursor);
+                expect(
+                    result[resultCollectionIndex].length,
+                ).toBeGreaterThanOrEqual(25);
+
+                // Test count with match returns a non-empty list
+                result = await client.hscan(key1, initialCursor, {
+                    match: "1*",
+                    count: 30,
+                });
+                expect(result[resultCursorIndex]).not.toEqual(initialCursor);
+                expect(result[resultCollectionIndex].length).toBeGreaterThan(0);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `hscan and sscan empty set, negative cursor, negative count, and non-hash key exception tests`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key1 = "{key}-1" + uuidv4();
+                const key2 = "{key}-2" + uuidv4();
+                const initialCursor = "0";
+                const resultCursorIndex = 0;
+                const resultCollectionIndex = 1;
+
+                // Empty set
+                let result = await client.hscan(key1, initialCursor);
+                expect(result[resultCursorIndex]).toEqual(initialCursor);
+                expect(result[resultCollectionIndex]).toEqual([]);
+
+                result = await client.sscan(key1, initialCursor);
+                expect(result[resultCursorIndex]).toEqual(initialCursor);
+                expect(result[resultCollectionIndex]).toEqual([]);
+
+                // Negative cursor
+                result = await client.hscan(key1, "-1");
+                expect(result[resultCursorIndex]).toEqual(initialCursor);
+                expect(result[resultCollectionIndex]).toEqual([]);
+
+                result = await client.sscan(key1, "-1");
+                expect(result[resultCursorIndex]).toEqual(initialCursor);
+                expect(result[resultCollectionIndex]).toEqual([]);
+
+                // Exceptions
+                // Non-hash key
+                expect(await client.set(key2, "test")).toEqual("OK");
+                await expect(client.hscan(key2, initialCursor)).rejects.toThrow(
+                    RequestError,
+                );
+                await expect(
+                    client.hscan(key2, initialCursor, {
+                        match: "test",
+                        count: 20,
+                    }),
+                ).rejects.toThrow(RequestError);
+
+                await expect(client.sscan(key2, initialCursor)).rejects.toThrow(
+                    RequestError,
+                );
+                await expect(
+                    client.sscan(key2, initialCursor, {
+                        match: "test",
+                        count: 30,
+                    }),
+                ).rejects.toThrow(RequestError);
+
+                // Negative count
+                await expect(
+                    client.hscan(key2, initialCursor, {
+                        count: -1,
+                    }),
+                ).rejects.toThrow(RequestError);
+
+                await expect(
+                    client.sscan(key2, initialCursor, {
+                        count: -1,
+                    }),
+                ).rejects.toThrow(RequestError);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `encoder test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key = uuidv4();
+                const value = uuidv4();
+                const valueEncoded = Buffer.from(value);
+
+                expect(await client.set(key, value)).toEqual("OK");
+                expect(await client.get(key)).toEqual(value);
+                expect(await client.get(key, Decoder.Bytes)).toEqual(
+                    valueEncoded,
+                );
+                expect(await client.get(key, Decoder.String)).toEqual(value);
+
+                // Setting the encoded value. Should behave as the previous test since the default is String decoding.
+                expect(await client.set(key, valueEncoded)).toEqual("OK");
+                expect(await client.get(key)).toEqual(value);
+                expect(await client.get(key, Decoder.Bytes)).toEqual(
+                    valueEncoded,
+                );
+                expect(await client.get(key, Decoder.String)).toEqual(value);
             }, protocol);
         },
         config.timeout,
@@ -1465,6 +1802,7 @@ export function runBaseTests(config: {
         async (protocol) => {
             await runTest(async (client: BaseClient) => {
                 const key1 = uuidv4();
+                const key2 = uuidv4();
                 const field1 = uuidv4();
                 const field2 = uuidv4();
                 const fieldValueMap = {
@@ -1472,11 +1810,25 @@ export function runBaseTests(config: {
                     [field2]: "value2",
                 };
 
+                const value1Encoded = Buffer.from("value1");
+                const value2Encoded = Buffer.from("value2");
+
                 expect(await client.hset(key1, fieldValueMap)).toEqual(2);
                 expect(await client.hvals(key1)).toEqual(["value1", "value2"]);
                 expect(await client.hdel(key1, [field1])).toEqual(1);
                 expect(await client.hvals(key1)).toEqual(["value2"]);
                 expect(await client.hvals("nonExistingHash")).toEqual([]);
+
+                //hvals with binary buffers
+                expect(await client.hset(key2, fieldValueMap)).toEqual(2);
+                expect(await client.hvals(key2, Decoder.Bytes)).toEqual([
+                    value1Encoded,
+                    value2Encoded,
+                ]);
+                expect(await client.hdel(key2, [field1])).toEqual(1);
+                expect(await client.hvals(key2, Decoder.Bytes)).toEqual([
+                    value2Encoded,
+                ]);
             }, protocol);
         },
         config.timeout,
@@ -1532,6 +1884,63 @@ export function runBaseTests(config: {
         config.timeout,
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `hrandfield test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient, cluster) => {
+                if (cluster.checkIfServerVersionLessThan("6.2.0")) {
+                    return;
+                }
+
+                const key1 = uuidv4();
+                const key2 = uuidv4();
+
+                // key does not exist
+                expect(await client.hrandfield(key1)).toBeNull();
+                expect(await client.hrandfieldCount(key1, 5)).toEqual([]);
+                expect(await client.hrandfieldWithValues(key1, 5)).toEqual([]);
+
+                const data = { "f 1": "v 1", "f 2": "v 2", "f 3": "v 3" };
+                const fields = Object.keys(data);
+                const entries = Object.entries(data);
+                expect(await client.hset(key1, data)).toEqual(3);
+
+                expect(fields).toContain(await client.hrandfield(key1));
+
+                // With Count - positive count
+                let result = await client.hrandfieldCount(key1, 5);
+                expect(result).toEqual(fields);
+
+                // With Count - negative count
+                result = await client.hrandfieldCount(key1, -5);
+                expect(result.length).toEqual(5);
+                result.map((r) => expect(fields).toContain(r));
+
+                // With values - positive count
+                let result2 = await client.hrandfieldWithValues(key1, 5);
+                expect(result2).toEqual(entries);
+
+                // With values - negative count
+                result2 = await client.hrandfieldWithValues(key1, -5);
+                expect(result2.length).toEqual(5);
+                result2.map((r) => expect(entries).toContainEqual(r));
+
+                // key exists but holds non hash type value
+                expect(await client.set(key2, "value")).toEqual("OK");
+                await expect(client.hrandfield(key2)).rejects.toThrow(
+                    RequestError,
+                );
+                await expect(client.hrandfieldCount(key2, 42)).rejects.toThrow(
+                    RequestError,
+                );
+                await expect(
+                    client.hrandfieldWithValues(key2, 42),
+                ).rejects.toThrow(RequestError);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
         `lpush, lpop and lrange with existing and non existing key_%p`,
         async (protocol) => {
@@ -2480,33 +2889,146 @@ export function runBaseTests(config: {
     );
 
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
-        `sunion test_%p`,
+        `sscan test_%p`,
         async (protocol) => {
             await runTest(async (client: BaseClient) => {
-                const key1 = `{key}:${uuidv4()}`;
-                const key2 = `{key}:${uuidv4()}`;
-                const stringKey = `{key}:${uuidv4()}`;
-                const nonExistingKey = `{key}:${uuidv4()}`;
-                const memberList1 = ["a", "b", "c"];
-                const memberList2 = ["b", "c", "d", "e"];
+                const key1 = "{key}-1" + uuidv4();
+                const initialCursor = "0";
+                const defaultCount = 10;
 
-                expect(await client.sadd(key1, memberList1)).toEqual(3);
-                expect(await client.sadd(key2, memberList2)).toEqual(4);
-                expect(await client.sunion([key1, key2])).toEqual(
-                    new Set(["a", "b", "c", "d", "e"]),
-                );
+                const numberMembers: string[] = [];
 
-                // invalid argument - key list must not be empty
-                await expect(client.sunion([])).rejects.toThrow();
+                for (let i = 0; i < 50000; i++) {
+                    numberMembers[i] = i.toString();
+                }
 
-                // non-existing key returns the set of existing keys
-                expect(await client.sunion([key1, nonExistingKey])).toEqual(
-                    new Set(memberList1),
-                );
+                const numberMembersSet: string[] = numberMembers;
+                const charMembers: string[] = ["a", "b", "c", "d", "e"];
+                const charMembersSet: Set = new Set(charMembers);
+                const resultCursorIndex = 0;
+                const resultCollectionIndex = 1;
 
-                // key exists, but it is not a set
-                expect(await client.set(stringKey, "foo")).toEqual("OK");
-                await expect(client.sunion([stringKey])).rejects.toThrow();
+                // Result contains the whole set
+                expect(await client.sadd(key1, charMembers)).toEqual(
+                    charMembers.length,
+                );
+                let result = await client.sscan(key1, initialCursor);
+                expect(await result[resultCursorIndex]).toEqual(initialCursor);
+                expect(result[resultCollectionIndex].length).toEqual(
+                    charMembers.length,
+                );
+
+                const resultMembers = result[resultCollectionIndex] as string[];
+
+                const allResultMember = resultMembers.every((member) =>
+                    charMembersSet.has(member),
+                );
+                expect(allResultMember).toEqual(true);
+
+                // Testing sscan with match
+                result = await client.sscan(key1, initialCursor, {
+                    match: "a",
+                });
+                expect(result[resultCursorIndex]).toEqual(initialCursor);
+                expect(result[resultCollectionIndex]).toEqual(["a"]);
+
+                // Result contains a subset of the key
+                expect(await client.sadd(key1, numberMembers)).toEqual(
+                    numberMembers.length,
+                );
+
+                let resultCursor = "0";
+                let secondResultValues: string[] = [];
+
+                let isFirstLoop = true;
+
+                do {
+                    result = await client.sscan(key1, resultCursor);
+                    resultCursor = result[resultCursorIndex].toString();
+                    secondResultValues = result[resultCollectionIndex];
+
+                    if (isFirstLoop) {
+                        expect(resultCursor).not.toBe("0");
+                        isFirstLoop = false;
+                    } else if (resultCursor === initialCursor) {
+                        break;
+                    }
+
+                    // Scan with result cursor has a different set
+                    const secondResult = await client.sscan(key1, resultCursor);
+                    const newResultCursor =
+                        secondResult[resultCursorIndex].toString();
+                    expect(resultCursor).not.toBe(newResultCursor);
+                    resultCursor = newResultCursor;
+                    expect(result[resultCollectionIndex]).not.toBe(
+                        secondResult[resultCollectionIndex],
+                    );
+                    secondResultValues = secondResult[resultCollectionIndex];
+                } while (resultCursor != initialCursor); // 0 is returned for the cursor of the last iteration.
+
+                const allSecondResultValues = Object.keys(
+                    secondResultValues,
+                ).every((value) => value in numberMembersSet);
+                expect(allSecondResultValues).toEqual(true);
+
+                // Test match pattern
+                result = await client.sscan(key1, initialCursor, {
+                    match: "*",
+                });
+                expect(result[resultCursorIndex]).not.toEqual(initialCursor);
+                expect(
+                    result[resultCollectionIndex].length,
+                ).toBeGreaterThanOrEqual(defaultCount);
+
+                // Test count
+                result = await client.sscan(key1, initialCursor, { count: 20 });
+                expect(result[resultCursorIndex]).not.toEqual(0);
+                expect(
+                    result[resultCollectionIndex].length,
+                ).toBeGreaterThanOrEqual(20);
+
+                // Test count with match returns a non-empty list
+                result = await client.sscan(key1, initialCursor, {
+                    match: "1*",
+                    count: 30,
+                });
+                expect(result[resultCursorIndex]).not.toEqual(initialCursor);
+                expect(
+                    result[resultCollectionIndex].length,
+                ).toBeGreaterThanOrEqual(0);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `sunion test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key1 = `{key}:${uuidv4()}`;
+                const key2 = `{key}:${uuidv4()}`;
+                const stringKey = `{key}:${uuidv4()}`;
+                const nonExistingKey = `{key}:${uuidv4()}`;
+                const memberList1 = ["a", "b", "c"];
+                const memberList2 = ["b", "c", "d", "e"];
+
+                expect(await client.sadd(key1, memberList1)).toEqual(3);
+                expect(await client.sadd(key2, memberList2)).toEqual(4);
+                expect(await client.sunion([key1, key2])).toEqual(
+                    new Set(["a", "b", "c", "d", "e"]),
+                );
+
+                // invalid argument - key list must not be empty
+                await expect(client.sunion([])).rejects.toThrow();
+
+                // non-existing key returns the set of existing keys
+                expect(await client.sunion([key1, nonExistingKey])).toEqual(
+                    new Set(memberList1),
+                );
+
+                // key exists, but it is not a set
+                expect(await client.set(stringKey, "foo")).toEqual("OK");
+                await expect(client.sunion([stringKey])).rejects.toThrow();
             }, protocol);
         },
         config.timeout,
@@ -2656,6 +3178,48 @@ export function runBaseTests(config: {
         config.timeout,
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `srandmember and srandmemberCount test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key = uuidv4();
+                const members = ["member1", "member2", "member3"];
+                expect(await client.sadd(key, members)).toEqual(3);
+
+                const result2 = await client.srandmember(key);
+                expect(members).toContain(result2);
+                expect(await client.srandmember("nonExistingKey")).toEqual(
+                    null,
+                );
+
+                // unique values are expected as count is positive
+                let result = await client.srandmemberCount(key, 4);
+                expect(result.length).toEqual(3);
+                expect(new Set(result)).toEqual(new Set(members));
+
+                // duplicate values are expected as count is negative
+                result = await client.srandmemberCount(key, -4);
+                expect(result.length).toEqual(4);
+                result.forEach((member) => {
+                    expect(members).toContain(member);
+                });
+
+                // empty return values for non-existing or empty keys
+                result = await client.srandmemberCount(key, 0);
+                expect(result.length).toEqual(0);
+                expect(result).toEqual([]);
+                expect(
+                    await client.srandmemberCount("nonExistingKey", 0),
+                ).toEqual([]);
+
+                expect(await client.set(key, "value")).toBe("OK");
+                await expect(client.srandmember(key)).rejects.toThrow();
+                await expect(client.srandmemberCount(key, 2)).rejects.toThrow();
+            }, protocol);
+        },
+        config.timeout,
+    );
+
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
         `exists with existing keys, an non existing key_%p`,
         async (protocol) => {
@@ -3354,8 +3918,8 @@ export function runBaseTests(config: {
                 expect(
                     await client.zcount(
                         key1,
-                        InfScoreBoundary.NegativeInfinity,
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
                 ).toEqual(3);
                 expect(
@@ -3373,28 +3937,20 @@ export function runBaseTests(config: {
                     ),
                 ).toEqual(2);
                 expect(
-                    await client.zcount(
-                        key1,
-                        InfScoreBoundary.NegativeInfinity,
-                        {
-                            value: 3,
-                        },
-                    ),
+                    await client.zcount(key1, InfBoundary.NegativeInfinity, {
+                        value: 3,
+                    }),
                 ).toEqual(3);
                 expect(
-                    await client.zcount(
-                        key1,
-                        InfScoreBoundary.PositiveInfinity,
-                        {
-                            value: 3,
-                        },
-                    ),
+                    await client.zcount(key1, InfBoundary.PositiveInfinity, {
+                        value: 3,
+                    }),
                 ).toEqual(0);
                 expect(
                     await client.zcount(
                         "nonExistingKey",
-                        InfScoreBoundary.NegativeInfinity,
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
                 ).toEqual(0);
 
@@ -3402,8 +3958,8 @@ export function runBaseTests(config: {
                 await expect(
                     client.zcount(
                         key2,
-                        InfScoreBoundary.NegativeInfinity,
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
                 ).rejects.toThrow();
             }, protocol);
@@ -3458,14 +4014,14 @@ export function runBaseTests(config: {
 
                 expect(
                     await client.zrange(key, {
-                        start: InfScoreBoundary.NegativeInfinity,
+                        start: InfBoundary.NegativeInfinity,
                         stop: { value: 3, isInclusive: false },
                         type: "byScore",
                     }),
                 ).toEqual(["one", "two"]);
                 const result = await client.zrangeWithScores(key, {
-                    start: InfScoreBoundary.NegativeInfinity,
-                    stop: InfScoreBoundary.PositiveInfinity,
+                    start: InfBoundary.NegativeInfinity,
+                    stop: InfBoundary.PositiveInfinity,
                     type: "byScore",
                 });
 
@@ -3481,7 +4037,7 @@ export function runBaseTests(config: {
                         key,
                         {
                             start: { value: 3, isInclusive: false },
-                            stop: InfScoreBoundary.NegativeInfinity,
+                            stop: InfBoundary.NegativeInfinity,
                             type: "byScore",
                         },
                         true,
@@ -3490,8 +4046,8 @@ export function runBaseTests(config: {
 
                 expect(
                     await client.zrange(key, {
-                        start: InfScoreBoundary.NegativeInfinity,
-                        stop: InfScoreBoundary.PositiveInfinity,
+                        start: InfBoundary.NegativeInfinity,
+                        stop: InfBoundary.PositiveInfinity,
                         limit: { offset: 1, count: 2 },
                         type: "byScore",
                     }),
@@ -3501,7 +4057,7 @@ export function runBaseTests(config: {
                     await client.zrange(
                         key,
                         {
-                            start: InfScoreBoundary.NegativeInfinity,
+                            start: InfBoundary.NegativeInfinity,
                             stop: { value: 3, isInclusive: false },
                             type: "byScore",
                         },
@@ -3511,7 +4067,7 @@ export function runBaseTests(config: {
 
                 expect(
                     await client.zrange(key, {
-                        start: InfScoreBoundary.PositiveInfinity,
+                        start: InfBoundary.PositiveInfinity,
                         stop: { value: 3, isInclusive: false },
                         type: "byScore",
                     }),
@@ -3521,7 +4077,7 @@ export function runBaseTests(config: {
                     await client.zrangeWithScores(
                         key,
                         {
-                            start: InfScoreBoundary.NegativeInfinity,
+                            start: InfBoundary.NegativeInfinity,
                             stop: { value: 3, isInclusive: false },
                             type: "byScore",
                         },
@@ -3531,7 +4087,7 @@ export function runBaseTests(config: {
 
                 expect(
                     await client.zrangeWithScores(key, {
-                        start: InfScoreBoundary.PositiveInfinity,
+                        start: InfBoundary.PositiveInfinity,
                         stop: { value: 3, isInclusive: false },
                         type: "byScore",
                     }),
@@ -3551,7 +4107,7 @@ export function runBaseTests(config: {
 
                 expect(
                     await client.zrange(key, {
-                        start: InfScoreBoundary.NegativeInfinity,
+                        start: InfBoundary.NegativeInfinity,
                         stop: { value: "c", isInclusive: false },
                         type: "byLex",
                     }),
@@ -3559,8 +4115,8 @@ export function runBaseTests(config: {
 
                 expect(
                     await client.zrange(key, {
-                        start: InfScoreBoundary.NegativeInfinity,
-                        stop: InfScoreBoundary.PositiveInfinity,
+                        start: InfBoundary.NegativeInfinity,
+                        stop: InfBoundary.PositiveInfinity,
                         limit: { offset: 1, count: 2 },
                         type: "byLex",
                     }),
@@ -3571,7 +4127,7 @@ export function runBaseTests(config: {
                         key,
                         {
                             start: { value: "c", isInclusive: false },
-                            stop: InfScoreBoundary.NegativeInfinity,
+                            stop: InfBoundary.NegativeInfinity,
                             type: "byLex",
                         },
                         true,
@@ -3582,7 +4138,7 @@ export function runBaseTests(config: {
                     await client.zrange(
                         key,
                         {
-                            start: InfScoreBoundary.NegativeInfinity,
+                            start: InfBoundary.NegativeInfinity,
                             stop: { value: "c", isInclusive: false },
                             type: "byLex",
                         },
@@ -3592,7 +4148,7 @@ export function runBaseTests(config: {
 
                 expect(
                     await client.zrange(key, {
-                        start: InfScoreBoundary.PositiveInfinity,
+                        start: InfBoundary.PositiveInfinity,
                         stop: { value: "c", isInclusive: false },
                         type: "byLex",
                     }),
@@ -3603,95 +4159,344 @@ export function runBaseTests(config: {
     );
 
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
-        `zrange different typesn of keys test_%p`,
+        `zrangeStore by index test_%p`,
         async (protocol) => {
-            await runTest(async (client: BaseClient) => {
-                const key = uuidv4();
+            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
+                if (cluster.checkIfServerVersionLessThan("6.2.0")) return;
+
+                const key = "{testKey}:1-" + uuidv4();
+                const destkey = "{testKey}:2-" + uuidv4();
+                const membersScores = { one: 1, two: 2, three: 3 };
+                expect(await client.zadd(key, membersScores)).toEqual(3);
+
                 expect(
-                    await client.zrange("nonExistingKey", {
+                    await client.zrangeStore(destkey, key, {
                         start: 0,
                         stop: 1,
                     }),
-                ).toEqual([]);
-
+                ).toEqual(2);
                 expect(
-                    await client.zrangeWithScores("nonExistingKey", {
+                    await client.zrange(destkey, {
                         start: 0,
-                        stop: 1,
+                        stop: -1,
                     }),
-                ).toEqual({});
-
-                expect(await client.set(key, "value")).toEqual("OK");
+                ).toEqual(["one", "two"]);
 
-                await expect(
-                    client.zrange(key, { start: 0, stop: 1 }),
-                ).rejects.toThrow();
+                expect(
+                    await client.zrangeStore(
+                        destkey,
+                        key,
+                        { start: 0, stop: 1 },
+                        true,
+                    ),
+                ).toEqual(2);
+                expect(
+                    await client.zrange(
+                        destkey,
+                        {
+                            start: 0,
+                            stop: -1,
+                        },
+                        true,
+                    ),
+                ).toEqual(["three", "two"]);
 
-                await expect(
-                    client.zrangeWithScores(key, { start: 0, stop: 1 }),
-                ).rejects.toThrow();
+                expect(
+                    await client.zrangeStore(destkey, key, {
+                        start: 3,
+                        stop: 1,
+                    }),
+                ).toEqual(0);
             }, protocol);
         },
         config.timeout,
     );
 
-    // Zinterstore command tests
-    async function zinterstoreWithAggregation(client: BaseClient) {
-        const key1 = "{testKey}:1-" + uuidv4();
-        const key2 = "{testKey}:2-" + uuidv4();
-        const key3 = "{testKey}:3-" + uuidv4();
-        const range = {
-            start: 0,
-            stop: -1,
-        };
-
-        const membersScores1 = { one: 1.0, two: 2.0 };
-        const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 };
-
-        expect(await client.zadd(key1, membersScores1)).toEqual(2);
-        expect(await client.zadd(key2, membersScores2)).toEqual(3);
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `zrangeStore by score test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
+                if (cluster.checkIfServerVersionLessThan("6.2.0")) return;
+                const key = "{testKey}:1-" + uuidv4();
+                const destkey = "{testKey}:2-" + uuidv4();
+                const membersScores = { one: 1, two: 2, three: 3 };
+                expect(await client.zadd(key, membersScores)).toEqual(3);
 
-        // Intersection results are aggregated by the MAX score of elements
-        expect(await client.zinterstore(key3, [key1, key2], "MAX")).toEqual(2);
-        const zinterstoreMapMax = await client.zrangeWithScores(key3, range);
-        const expectedMapMax = {
-            one: 2,
-            two: 3,
-        };
-        expect(compareMaps(zinterstoreMapMax, expectedMapMax)).toBe(true);
+                expect(
+                    await client.zrangeStore(destkey, key, {
+                        start: InfBoundary.NegativeInfinity,
+                        stop: { value: 3, isInclusive: false },
+                        type: "byScore",
+                    }),
+                ).toEqual(2);
+                expect(
+                    await client.zrange(destkey, {
+                        start: 0,
+                        stop: -1,
+                    }),
+                ).toEqual(["one", "two"]);
 
-        // Intersection results are aggregated by the MIN score of elements
-        expect(await client.zinterstore(key3, [key1, key2], "MIN")).toEqual(2);
-        const zinterstoreMapMin = await client.zrangeWithScores(key3, range);
-        const expectedMapMin = {
-            one: 1,
-            two: 2,
-        };
-        expect(compareMaps(zinterstoreMapMin, expectedMapMin)).toBe(true);
+                expect(
+                    await client.zrangeStore(
+                        destkey,
+                        key,
+                        {
+                            start: { value: 3, isInclusive: false },
+                            stop: InfBoundary.NegativeInfinity,
+                            type: "byScore",
+                        },
+                        true,
+                    ),
+                ).toEqual(2);
+                expect(
+                    await client.zrange(
+                        destkey,
+                        {
+                            start: 0,
+                            stop: -1,
+                        },
+                        true,
+                    ),
+                ).toEqual(["two", "one"]);
 
-        // Intersection results are aggregated by the SUM score of elements
-        expect(await client.zinterstore(key3, [key1, key2], "SUM")).toEqual(2);
-        const zinterstoreMapSum = await client.zrangeWithScores(key3, range);
-        const expectedMapSum = {
-            one: 3,
-            two: 5,
-        };
-        expect(compareMaps(zinterstoreMapSum, expectedMapSum)).toBe(true);
-    }
+                expect(
+                    await client.zrangeStore(destkey, key, {
+                        start: InfBoundary.NegativeInfinity,
+                        stop: InfBoundary.PositiveInfinity,
+                        limit: { offset: 1, count: 2 },
+                        type: "byScore",
+                    }),
+                ).toEqual(2);
+                expect(
+                    await client.zrange(destkey, {
+                        start: 0,
+                        stop: -1,
+                    }),
+                ).toEqual(["two", "three"]);
 
-    async function zinterstoreBasicTest(client: BaseClient) {
-        const key1 = "{testKey}:1-" + uuidv4();
-        const key2 = "{testKey}:2-" + uuidv4();
-        const key3 = "{testKey}:3-" + uuidv4();
-        const range = {
-            start: 0,
-            stop: -1,
-        };
+                expect(
+                    await client.zrangeStore(
+                        destkey,
+                        key,
+                        {
+                            start: InfBoundary.NegativeInfinity,
+                            stop: { value: 3, isInclusive: false },
+                            type: "byScore",
+                        },
+                        true,
+                    ),
+                ).toEqual(0);
 
-        const membersScores1 = { one: 1.0, two: 2.0 };
-        const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 };
+                expect(
+                    await client.zrangeStore(destkey, key, {
+                        start: InfBoundary.PositiveInfinity,
+                        stop: { value: 3, isInclusive: false },
+                        type: "byScore",
+                    }),
+                ).toEqual(0);
+            }, protocol);
+        },
+        config.timeout,
+    );
 
-        expect(await client.zadd(key1, membersScores1)).toEqual(2);
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `zrangeStore by lex test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
+                if (cluster.checkIfServerVersionLessThan("6.2.0")) return;
+                const key = "{testKey}:1-" + uuidv4();
+                const destkey = "{testKey}:2-" + uuidv4();
+                const membersScores = { a: 1, b: 2, c: 3 };
+                expect(await client.zadd(key, membersScores)).toEqual(3);
+
+                expect(
+                    await client.zrangeStore(destkey, key, {
+                        start: InfBoundary.NegativeInfinity,
+                        stop: { value: "c", isInclusive: false },
+                        type: "byLex",
+                    }),
+                ).toEqual(2);
+                expect(
+                    await client.zrange(destkey, {
+                        start: 0,
+                        stop: -1,
+                    }),
+                ).toEqual(["a", "b"]);
+
+                expect(
+                    await client.zrangeStore(destkey, key, {
+                        start: InfBoundary.NegativeInfinity,
+                        stop: InfBoundary.PositiveInfinity,
+                        limit: { offset: 1, count: 2 },
+                        type: "byLex",
+                    }),
+                ).toEqual(2);
+                expect(
+                    await client.zrange(destkey, {
+                        start: 0,
+                        stop: -1,
+                    }),
+                ).toEqual(["b", "c"]);
+
+                expect(
+                    await client.zrangeStore(
+                        destkey,
+                        key,
+                        {
+                            start: { value: "c", isInclusive: false },
+                            stop: InfBoundary.NegativeInfinity,
+                            type: "byLex",
+                        },
+                        true,
+                    ),
+                ).toEqual(2);
+                expect(
+                    await client.zrange(
+                        destkey,
+                        {
+                            start: 0,
+                            stop: -1,
+                        },
+                        true,
+                    ),
+                ).toEqual(["b", "a"]);
+
+                expect(
+                    await client.zrangeStore(
+                        destkey,
+                        key,
+                        {
+                            start: InfBoundary.NegativeInfinity,
+                            stop: { value: "c", isInclusive: false },
+                            type: "byLex",
+                        },
+                        true,
+                    ),
+                ).toEqual(0);
+
+                expect(
+                    await client.zrangeStore(destkey, key, {
+                        start: InfBoundary.PositiveInfinity,
+                        stop: { value: "c", isInclusive: false },
+                        type: "byLex",
+                    }),
+                ).toEqual(0);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `zrange and zrangeStore different types of keys test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
+                const key = "{testKey}:1-" + uuidv4();
+                const nonExistingKey = "{testKey}:2-" + uuidv4();
+                const destkey = "{testKey}:3-" + uuidv4();
+
+                // test non-existing key - return an empty set
+                expect(
+                    await client.zrange(nonExistingKey, {
+                        start: 0,
+                        stop: 1,
+                    }),
+                ).toEqual([]);
+
+                expect(
+                    await client.zrangeWithScores(nonExistingKey, {
+                        start: 0,
+                        stop: 1,
+                    }),
+                ).toEqual({});
+
+                // test against a non-sorted set - throw RequestError
+                expect(await client.set(key, "value")).toEqual("OK");
+
+                await expect(
+                    client.zrange(key, { start: 0, stop: 1 }),
+                ).rejects.toThrow();
+
+                await expect(
+                    client.zrangeWithScores(key, { start: 0, stop: 1 }),
+                ).rejects.toThrow();
+
+                // test zrangeStore - added in version 6.2.0
+                if (cluster.checkIfServerVersionLessThan("6.2.0")) return;
+
+                // test non-existing key - stores an empty set
+                expect(
+                    await client.zrangeStore(destkey, nonExistingKey, {
+                        start: 0,
+                        stop: 1,
+                    }),
+                ).toEqual(0);
+
+                // test against a non-sorted set - throw RequestError
+                await expect(
+                    client.zrangeStore(destkey, key, { start: 0, stop: 1 }),
+                ).rejects.toThrow();
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    // Zinterstore command tests
+    async function zinterstoreWithAggregation(client: BaseClient) {
+        const key1 = "{testKey}:1-" + uuidv4();
+        const key2 = "{testKey}:2-" + uuidv4();
+        const key3 = "{testKey}:3-" + uuidv4();
+        const range = {
+            start: 0,
+            stop: -1,
+        };
+
+        const membersScores1 = { one: 1.0, two: 2.0 };
+        const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 };
+
+        expect(await client.zadd(key1, membersScores1)).toEqual(2);
+        expect(await client.zadd(key2, membersScores2)).toEqual(3);
+
+        // Intersection results are aggregated by the MAX score of elements
+        expect(await client.zinterstore(key3, [key1, key2], "MAX")).toEqual(2);
+        const zinterstoreMapMax = await client.zrangeWithScores(key3, range);
+        const expectedMapMax = {
+            one: 2,
+            two: 3,
+        };
+        expect(compareMaps(zinterstoreMapMax, expectedMapMax)).toBe(true);
+
+        // Intersection results are aggregated by the MIN score of elements
+        expect(await client.zinterstore(key3, [key1, key2], "MIN")).toEqual(2);
+        const zinterstoreMapMin = await client.zrangeWithScores(key3, range);
+        const expectedMapMin = {
+            one: 1,
+            two: 2,
+        };
+        expect(compareMaps(zinterstoreMapMin, expectedMapMin)).toBe(true);
+
+        // Intersection results are aggregated by the SUM score of elements
+        expect(await client.zinterstore(key3, [key1, key2], "SUM")).toEqual(2);
+        const zinterstoreMapSum = await client.zrangeWithScores(key3, range);
+        const expectedMapSum = {
+            one: 3,
+            two: 5,
+        };
+        expect(compareMaps(zinterstoreMapSum, expectedMapSum)).toBe(true);
+    }
+
+    async function zinterstoreBasicTest(client: BaseClient) {
+        const key1 = "{testKey}:1-" + uuidv4();
+        const key2 = "{testKey}:2-" + uuidv4();
+        const key3 = "{testKey}:3-" + uuidv4();
+        const range = {
+            start: 0,
+            stop: -1,
+        };
+
+        const membersScores1 = { one: 1.0, two: 2.0 };
+        const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 };
+
+        expect(await client.zadd(key1, membersScores1)).toEqual(2);
         expect(await client.zadd(key2, membersScores2)).toEqual(3);
 
         expect(await client.zinterstore(key3, [key1, key2])).toEqual(2);
@@ -3795,13 +4600,7 @@ export function runBaseTests(config: {
                 expect(await client.type(key)).toEqual("hash");
                 expect(await client.del([key])).toEqual(1);
 
-                await client.customCommand([
-                    "XADD",
-                    key,
-                    "*",
-                    "field",
-                    "value",
-                ]);
+                await client.xadd(key, [["field", "value"]]);
                 expect(await client.type(key)).toEqual("stream");
                 expect(await client.del([key])).toEqual(1);
                 expect(await client.type(key)).toEqual("none");
@@ -3983,6 +4782,92 @@ export function runBaseTests(config: {
         config.timeout,
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `bzpopmax test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
+                const key1 = "{key}-1" + uuidv4();
+                const key2 = "{key}-2" + uuidv4();
+                const key3 = "{key}-3" + uuidv4();
+
+                expect(await client.zadd(key1, { a: 1.0, b: 1.5 })).toBe(2);
+                expect(await client.zadd(key2, { c: 2.0 })).toBe(1);
+                expect(await client.bzpopmax([key1, key2], 0.5)).toEqual([
+                    key1,
+                    "b",
+                    1.5,
+                ]);
+
+                // nothing popped out / key does not exist
+                expect(
+                    await client.bzpopmax(
+                        [key3],
+                        cluster.checkIfServerVersionLessThan("6.0.0")
+                            ? 1.0
+                            : 0.001,
+                    ),
+                ).toBeNull();
+
+                // pops from the second key
+                expect(await client.bzpopmax([key3, key2], 0.5)).toEqual([
+                    key2,
+                    "c",
+                    2.0,
+                ]);
+
+                // key exists but holds non-ZSET value
+                expect(await client.set(key3, "bzpopmax")).toBe("OK");
+                await expect(client.bzpopmax([key3], 0.5)).rejects.toThrow(
+                    RequestError,
+                );
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `bzpopmin test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
+                const key1 = "{key}-1" + uuidv4();
+                const key2 = "{key}-2" + uuidv4();
+                const key3 = "{key}-3" + uuidv4();
+
+                expect(await client.zadd(key1, { a: 1.0, b: 1.5 })).toBe(2);
+                expect(await client.zadd(key2, { c: 2.0 })).toBe(1);
+                expect(await client.bzpopmin([key1, key2], 0.5)).toEqual([
+                    key1,
+                    "a",
+                    1.0,
+                ]);
+
+                // nothing popped out / key does not exist
+                expect(
+                    await client.bzpopmin(
+                        [key3],
+                        cluster.checkIfServerVersionLessThan("6.0.0")
+                            ? 1.0
+                            : 0.001,
+                    ),
+                ).toBeNull();
+
+                // pops from the second key
+                expect(await client.bzpopmin([key3, key2], 0.5)).toEqual([
+                    key2,
+                    "c",
+                    2.0,
+                ]);
+
+                // key exists but holds non-ZSET value
+                expect(await client.set(key3, "bzpopmin")).toBe("OK");
+                await expect(client.bzpopmin([key3], 0.5)).rejects.toThrow(
+                    RequestError,
+                );
+            }, protocol);
+        },
+        config.timeout,
+    );
+
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
         `Pttl test_%p`,
         async (protocol) => {
@@ -4326,44 +5211,169 @@ export function runBaseTests(config: {
     );
 
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
-        `zremRangeByLex test_%p`,
+        `xrange test_%p`,
         async (protocol) => {
-            await runTest(async (client: BaseClient) => {
+            await runTest(async (client: BaseClient, cluster) => {
                 const key = uuidv4();
+                const nonExistingKey = uuidv4();
                 const stringKey = uuidv4();
-                const membersScores = { a: 1, b: 2, c: 3, d: 4 };
-                expect(await client.zadd(key, membersScores)).toEqual(4);
+                const streamId1 = "0-1";
+                const streamId2 = "0-2";
+                const streamId3 = "0-3";
 
                 expect(
-                    await client.zremRangeByLex(
+                    await client.xadd(key, [["f1", "v1"]], { id: streamId1 }),
+                ).toEqual(streamId1);
+                expect(
+                    await client.xadd(key, [["f2", "v2"]], { id: streamId2 }),
+                ).toEqual(streamId2);
+                expect(await client.xlen(key)).toEqual(2);
+
+                // get everything from the stream
+                expect(
+                    await client.xrange(
                         key,
-                        { value: "a", isInclusive: false },
-                        { value: "c" },
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
-                ).toEqual(2);
+                ).toEqual({
+                    [streamId1]: [["f1", "v1"]],
+                    [streamId2]: [["f2", "v2"]],
+                });
 
+                // returns empty mapping if + before -
                 expect(
-                    await client.zremRangeByLex(
+                    await client.xrange(
                         key,
-                        { value: "d" },
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.PositiveInfinity,
+                        InfBoundary.NegativeInfinity,
                     ),
-                ).toEqual(1);
+                ).toEqual({});
 
-                // MinLex > MaxLex
                 expect(
-                    await client.zremRangeByLex(
+                    await client.xadd(key, [["f3", "v3"]], { id: streamId3 }),
+                ).toEqual(streamId3);
+
+                // get the newest entry
+                if (!cluster.checkIfServerVersionLessThan("6.2.0")) {
+                    expect(
+                        await client.xrange(
+                            key,
+                            { isInclusive: false, value: streamId2 },
+                            { value: "5" },
+                            1,
+                        ),
+                    ).toEqual({ [streamId3]: [["f3", "v3"]] });
+                }
+
+                // xrange against an emptied stream
+                expect(
+                    await client.xdel(key, [streamId1, streamId2, streamId3]),
+                ).toEqual(3);
+                expect(
+                    await client.xrange(
                         key,
-                        { value: "a" },
-                        InfScoreBoundary.NegativeInfinity,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
+                        10,
                     ),
-                ).toEqual(0);
+                ).toEqual({});
+
+                expect(
+                    await client.xrange(
+                        nonExistingKey,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
+                    ),
+                ).toEqual({});
+
+                // count value < 1 returns null
+                expect(
+                    await client.xrange(
+                        key,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
+                        0,
+                    ),
+                ).toEqual(null);
+                expect(
+                    await client.xrange(
+                        key,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
+                        -1,
+                    ),
+                ).toEqual(null);
+
+                // key exists, but it is not a stream
+                expect(await client.set(stringKey, "foo"));
+                await expect(
+                    client.xrange(
+                        stringKey,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
+                    ),
+                ).rejects.toThrow(RequestError);
+
+                // invalid start bound
+                await expect(
+                    client.xrange(
+                        key,
+                        { value: "not_a_stream_id" },
+                        InfBoundary.PositiveInfinity,
+                    ),
+                ).rejects.toThrow(RequestError);
+
+                // invalid end bound
+                await expect(
+                    client.xrange(key, InfBoundary.NegativeInfinity, {
+                        value: "not_a_stream_id",
+                    }),
+                ).rejects.toThrow(RequestError);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `zremRangeByLex test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key = uuidv4();
+                const stringKey = uuidv4();
+                const membersScores = { a: 1, b: 2, c: 3, d: 4 };
+                expect(await client.zadd(key, membersScores)).toEqual(4);
+
+                expect(
+                    await client.zremRangeByLex(
+                        key,
+                        { value: "a", isInclusive: false },
+                        { value: "c" },
+                    ),
+                ).toEqual(2);
+
+                expect(
+                    await client.zremRangeByLex(
+                        key,
+                        { value: "d" },
+                        InfBoundary.PositiveInfinity,
+                    ),
+                ).toEqual(1);
+
+                // MinLex > MaxLex
+                expect(
+                    await client.zremRangeByLex(
+                        key,
+                        { value: "a" },
+                        InfBoundary.NegativeInfinity,
+                    ),
+                ).toEqual(0);
 
                 expect(
                     await client.zremRangeByLex(
                         "nonExistingKey",
-                        InfScoreBoundary.NegativeInfinity,
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
                 ).toEqual(0);
 
@@ -4372,8 +5382,8 @@ export function runBaseTests(config: {
                 await expect(
                     client.zremRangeByLex(
                         stringKey,
-                        InfScoreBoundary.NegativeInfinity,
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
                 ).rejects.toThrow(RequestError);
             }, protocol);
@@ -4401,15 +5411,15 @@ export function runBaseTests(config: {
                     await client.zremRangeByScore(
                         key,
                         { value: 1 },
-                        InfScoreBoundary.NegativeInfinity,
+                        InfBoundary.NegativeInfinity,
                     ),
                 ).toEqual(0);
 
                 expect(
                     await client.zremRangeByScore(
                         "nonExistingKey",
-                        InfScoreBoundary.NegativeInfinity,
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
                 ).toEqual(0);
             }, protocol);
@@ -4430,8 +5440,8 @@ export function runBaseTests(config: {
                 expect(
                     await client.zlexcount(
                         key,
-                        InfScoreBoundary.NegativeInfinity,
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
                 ).toEqual(3);
 
@@ -4440,40 +5450,32 @@ export function runBaseTests(config: {
                     await client.zlexcount(
                         key,
                         { value: "a", isInclusive: false },
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
                 ).toEqual(2);
 
                 // In range negative infinity to c (inclusive)
                 expect(
-                    await client.zlexcount(
-                        key,
-                        InfScoreBoundary.NegativeInfinity,
-                        {
-                            value: "c",
-                            isInclusive: true,
-                        },
-                    ),
+                    await client.zlexcount(key, InfBoundary.NegativeInfinity, {
+                        value: "c",
+                        isInclusive: true,
+                    }),
                 ).toEqual(3);
 
                 // Incorrect range start > end
                 expect(
-                    await client.zlexcount(
-                        key,
-                        InfScoreBoundary.PositiveInfinity,
-                        {
-                            value: "c",
-                            isInclusive: true,
-                        },
-                    ),
+                    await client.zlexcount(key, InfBoundary.PositiveInfinity, {
+                        value: "c",
+                        isInclusive: true,
+                    }),
                 ).toEqual(0);
 
                 // Non-existing key
                 expect(
                     await client.zlexcount(
                         "non_existing_key",
-                        InfScoreBoundary.NegativeInfinity,
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
                 ).toEqual(0);
 
@@ -4482,8 +5484,8 @@ export function runBaseTests(config: {
                 await expect(
                     client.zlexcount(
                         stringKey,
-                        InfScoreBoundary.NegativeInfinity,
-                        InfScoreBoundary.PositiveInfinity,
+                        InfBoundary.NegativeInfinity,
+                        InfBoundary.PositiveInfinity,
                     ),
                 ).rejects.toThrow(RequestError);
             }, protocol);
@@ -4568,6 +5570,215 @@ export function runBaseTests(config: {
         config.timeout,
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `xinfo stream test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key = uuidv4();
+                const groupName = `group-${uuidv4()}`;
+                const consumerName = `consumer-${uuidv4()}`;
+                const streamId0_0 = "0-0";
+                const streamId1_0 = "1-0";
+                const streamId1_1 = "1-1";
+
+                // Setup: add stream entry, create consumer group and consumer, read from stream with consumer
+                expect(
+                    await client.xadd(
+                        key,
+                        [
+                            ["a", "b"],
+                            ["c", "d"],
+                        ],
+                        { id: streamId1_0 },
+                    ),
+                ).toEqual(streamId1_0);
+
+                expect(
+                    await client.xgroupCreate(key, groupName, streamId0_0),
+                ).toEqual("OK");
+
+                // TODO: uncomment when XREADGROUP is implemented
+                // const xreadgroupResult = await client.xreadgroup([[key, ">"]], groupName, consumerName);
+                await client.customCommand([
+                    "XREADGROUP",
+                    "GROUP",
+                    groupName,
+                    consumerName,
+                    "STREAMS",
+                    key,
+                    ">",
+                ]);
+
+                // test xinfoStream base (non-full) case:
+                const result = (await client.xinfoStream(key)) as {
+                    length: number;
+                    "radix-tree-keys": number;
+                    "radix-tree-nodes": number;
+                    "last-generated-id": string;
+                    "max-deleted-entry-id": string;
+                    "entries-added": number;
+                    "recorded-first-entry-id": string;
+                    "first-entry": (string | number | string[])[];
+                    "last-entry": (string | number | string[])[];
+                    groups: number;
+                };
+
+                // verify result:
+                expect(result.length).toEqual(1);
+                const expectedFirstEntry = ["1-0", ["a", "b", "c", "d"]];
+                expect(result["first-entry"]).toEqual(expectedFirstEntry);
+                expect(result["last-entry"]).toEqual(expectedFirstEntry);
+                expect(result.groups).toEqual(1);
+
+                // Add one more entry
+                expect(
+                    await client.xadd(key, [["foo", "bar"]], {
+                        id: streamId1_1,
+                    }),
+                ).toEqual(streamId1_1);
+                const fullResult = (await client.xinfoStream(key, 1)) as {
+                    length: number;
+                    "radix-tree-keys": number;
+                    "radix-tree-nodes": number;
+                    "last-generated-id": string;
+                    "max-deleted-entry-id": string;
+                    "entries-added": number;
+                    "recorded-first-entry-id": string;
+                    entries: (string | number | string[])[][];
+                    groups: [
+                        {
+                            name: string;
+                            "last-delivered-id": string;
+                            "entries-read": number;
+                            lag: number;
+                            "pel-count": number;
+                            pending: (string | number)[][];
+                            consumers: [
+                                {
+                                    name: string;
+                                    "seen-time": number;
+                                    "active-time": number;
+                                    "pel-count": number;
+                                    pending: (string | number)[][];
+                                },
+                            ];
+                        },
+                    ];
+                };
+
+                // verify full result like:
+                // {
+                //   length: 2,
+                //   'radix-tree-keys': 1,
+                //   'radix-tree-nodes': 2,
+                //   'last-generated-id': '1-1',
+                //   'max-deleted-entry-id': '0-0',
+                //   'entries-added': 2,
+                //   'recorded-first-entry-id': '1-0',
+                //   entries: [ [ '1-0', ['a', 'b', ...] ] ],
+                //   groups: [ {
+                //     name: 'group',
+                //     'last-delivered-id': '1-0',
+                //     'entries-read': 1,
+                //     lag: 1,
+                //     'pel-count': 1,
+                //     pending: [ [ '1-0', 'consumer', 1722624726802, 1 ] ],
+                //     consumers: [ {
+                //         name: 'consumer',
+                //         'seen-time': 1722624726802,
+                //         'active-time': 1722624726802,
+                //         'pel-count': 1,
+                //         pending: [ [ '1-0', 'consumer', 1722624726802, 1 ] ],
+                //         }
+                //       ]
+                //     }
+                //   ]
+                // }
+                expect(fullResult.length).toEqual(2);
+                expect(fullResult["recorded-first-entry-id"]).toEqual(
+                    streamId1_0,
+                );
+
+                // Only the first entry will be returned since we passed count: 1
+                expect(fullResult.entries).toEqual([expectedFirstEntry]);
+
+                // compare groupName, consumerName, and pending messages from the full info result:
+                const fullResultGroups = fullResult.groups;
+                expect(fullResultGroups.length).toEqual(1);
+                expect(fullResultGroups[0]["name"]).toEqual(groupName);
+
+                const pendingResult = fullResultGroups[0]["pending"];
+                expect(pendingResult.length).toEqual(1);
+                expect(pendingResult[0][0]).toEqual(streamId1_0);
+                expect(pendingResult[0][1]).toEqual(consumerName);
+
+                const consumersResult = fullResultGroups[0]["consumers"];
+                expect(consumersResult.length).toEqual(1);
+                expect(consumersResult[0]["name"]).toEqual(consumerName);
+
+                const consumerPendingResult = fullResultGroups[0]["pending"];
+                expect(consumerPendingResult.length).toEqual(1);
+                expect(consumerPendingResult[0][0]).toEqual(streamId1_0);
+                expect(consumerPendingResult[0][1]).toEqual(consumerName);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `xinfo stream edge cases and failures test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key = `{key}-1-${uuidv4()}`;
+                const stringKey = `{key}-2-${uuidv4()}`;
+                const nonExistentKey = `{key}-3-${uuidv4()}`;
+                const streamId1_0 = "1-0";
+
+                // Setup: create empty stream
+                expect(
+                    await client.xadd(key, [["field", "value"]], {
+                        id: streamId1_0,
+                    }),
+                ).toEqual(streamId1_0);
+                expect(await client.xdel(key, [streamId1_0])).toEqual(1);
+
+                // XINFO STREAM called against empty stream
+                const result = await client.xinfoStream(key);
+                expect(result["length"]).toEqual(0);
+                expect(result["first-entry"]).toEqual(null);
+                expect(result["last-entry"]).toEqual(null);
+
+                // XINFO STREAM FULL called against empty stream. Negative count values are ignored.
+                const fullResult = await client.xinfoStream(key, -3);
+                expect(fullResult["length"]).toEqual(0);
+                expect(fullResult["entries"]).toEqual([]);
+                expect(fullResult["groups"]).toEqual([]);
+
+                // Calling XINFO STREAM with a non-existing key raises an error
+                await expect(
+                    client.xinfoStream(nonExistentKey),
+                ).rejects.toThrow();
+                await expect(
+                    client.xinfoStream(nonExistentKey, true),
+                ).rejects.toThrow();
+                await expect(
+                    client.xinfoStream(nonExistentKey, 2),
+                ).rejects.toThrow();
+
+                // Key exists, but it is not a stream
+                await client.set(stringKey, "boofar");
+                await expect(client.xinfoStream(stringKey)).rejects.toThrow();
+                await expect(
+                    client.xinfoStream(stringKey, true),
+                ).rejects.toThrow();
+                await expect(
+                    client.xinfoStream(stringKey, 2),
+                ).rejects.toThrow();
+            }, protocol);
+        },
+        config.timeout,
+    );
+
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
         "rename test_%p",
         async (protocol) => {
@@ -4617,6 +5828,116 @@ export function runBaseTests(config: {
         config.timeout,
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        "dump and restore test_%p",
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key1 = "{key}-1" + uuidv4();
+                const key2 = "{key}-2" + uuidv4();
+                const key3 = "{key}-3" + uuidv4();
+                const nonExistingkey = "{nonExistingkey}-" + uuidv4();
+                const value = "orange";
+                const valueEncode = Buffer.from(value);
+
+                expect(await client.set(key1, value)).toEqual("OK");
+
+                // Dump non-existing key
+                expect(await client.dump(nonExistingkey)).toBeNull();
+
+                // Dump existing key
+                const data = (await client.dump(key1)) as Buffer;
+                expect(data).not.toBeNull();
+
+                // Restore to a new key without option
+                expect(await client.restore(key2, 0, data)).toEqual("OK");
+                expect(await client.get(key2, Decoder.String)).toEqual(value);
+                expect(await client.get(key2, Decoder.Bytes)).toEqual(
+                    valueEncode,
+                );
+
+                // Restore to an existing key
+                await expect(client.restore(key2, 0, data)).rejects.toThrow(
+                    "BUSYKEY: Target key name already exists.",
+                );
+
+                // Restore with `REPLACE` and existing key holding different value
+                expect(await client.sadd(key3, ["a"])).toEqual(1);
+                expect(
+                    await client.restore(key3, 0, data, { replace: true }),
+                ).toEqual("OK");
+
+                // Restore with `REPLACE` option
+                expect(
+                    await client.restore(key2, 0, data, { replace: true }),
+                ).toEqual("OK");
+
+                // Restore with `REPLACE`, `ABSTTL`, and positive TTL
+                expect(
+                    await client.restore(key2, 1000, data, {
+                        replace: true,
+                        absttl: true,
+                    }),
+                ).toEqual("OK");
+
+                // Restore with `REPLACE`, `ABSTTL`, and negative TTL
+                await expect(
+                    client.restore(key2, -10, data, {
+                        replace: true,
+                        absttl: true,
+                    }),
+                ).rejects.toThrow("Invalid TTL value");
+
+                // Restore with REPLACE and positive idletime
+                expect(
+                    await client.restore(key2, 0, data, {
+                        replace: true,
+                        idletime: 10,
+                    }),
+                ).toEqual("OK");
+
+                // Restore with REPLACE and negative idletime
+                await expect(
+                    client.restore(key2, 0, data, {
+                        replace: true,
+                        idletime: -10,
+                    }),
+                ).rejects.toThrow("Invalid IDLETIME value");
+
+                // Restore with REPLACE and positive frequency
+                expect(
+                    await client.restore(key2, 0, data, {
+                        replace: true,
+                        frequency: 10,
+                    }),
+                ).toEqual("OK");
+
+                // Restore with REPLACE and negative frequency
+                await expect(
+                    client.restore(key2, 0, data, {
+                        replace: true,
+                        frequency: -10,
+                    }),
+                ).rejects.toThrow("Invalid FREQ value");
+
+                // Restore only uses IDLETIME or FREQ modifiers
+                // Error will be raised if both options are set
+                await expect(
+                    client.restore(key2, 0, data, {
+                        replace: true,
+                        idletime: 10,
+                        frequency: 10,
+                    }),
+                ).rejects.toThrow("syntax error");
+
+                // Restore with checksumto error
+                await expect(
+                    client.restore(key2, 0, valueEncode, { replace: true }),
+                ).rejects.toThrow("DUMP payload version or checksum are wrong");
+            }, protocol);
+        },
+        config.timeout,
+    );
+
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
         "pfadd test_%p",
         async (protocol) => {
@@ -4751,6 +6072,74 @@ export function runBaseTests(config: {
         config.timeout,
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        "append test_%p",
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key1 = uuidv4();
+                const key2 = uuidv4();
+                const key3 = uuidv4();
+                const value = uuidv4();
+                const valueEncoded = Buffer.from(value);
+
+                // Append on non-existing string(similar to SET)
+                expect(await client.append(key1, value)).toBe(value.length);
+                expect(await client.append(key1, value)).toBe(value.length * 2);
+                expect(await client.get(key1)).toEqual(value.concat(value));
+
+                // key exists but holding the wrong kind of value
+                expect(await client.sadd(key2, ["a"])).toBe(1);
+                await expect(client.append(key2, "_")).rejects.toThrow(
+                    RequestError,
+                );
+
+                // Key and value as buffers
+                expect(await client.append(key3, valueEncoded)).toBe(
+                    value.length,
+                );
+                expect(await client.append(key3, valueEncoded)).toBe(
+                    valueEncoded.length * 2,
+                );
+                expect(await client.get(key3, Decoder.Bytes)).toEqual(
+                    Buffer.concat([valueEncoded, valueEncoded]),
+                );
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        "wait test_%p",
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key = uuidv4();
+                const value1 = uuidv4();
+                const value2 = uuidv4();
+
+                // assert that wait returns 0 under standalone and 1 under cluster mode.
+                expect(await client.set(key, value1)).toEqual("OK");
+
+                if (client instanceof GlideClusterClient) {
+                    expect(await client.wait(1, 1000)).toBeGreaterThanOrEqual(
+                        1,
+                    );
+                } else {
+                    expect(await client.wait(1, 1000)).toBeGreaterThanOrEqual(
+                        0,
+                    );
+                }
+
+                // command should fail on a negative timeout value
+                await expect(client.wait(1, -1)).rejects.toThrow(RequestError);
+
+                // ensure that command doesn't time out even if timeout > request timeout (250ms by default)
+                expect(await client.set(key, value2)).toEqual("OK");
+                expect(await client.wait(100, 500)).toBeGreaterThanOrEqual(0);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
     // Set command tests
 
     async function setWithExpiryOptions(client: BaseClient) {
@@ -4758,7 +6147,7 @@ export function runBaseTests(config: {
         const value = uuidv4();
         const setResWithExpirySetMilli = await client.set(key, value, {
             expiry: {
-                type: "milliseconds",
+                type: TimeUnit.Milliseconds,
                 count: 500,
             },
         });
@@ -4768,7 +6157,7 @@ export function runBaseTests(config: {
 
         const setResWithExpirySec = await client.set(key, value, {
             expiry: {
-                type: "seconds",
+                type: TimeUnit.Seconds,
                 count: 1,
             },
         });
@@ -4778,7 +6167,7 @@ export function runBaseTests(config: {
 
         const setWithUnixSec = await client.set(key, value, {
             expiry: {
-                type: "unixSeconds",
+                type: TimeUnit.UnixSeconds,
                 count: Math.floor(Date.now() / 1000) + 1,
             },
         });
@@ -4800,7 +6189,7 @@ export function runBaseTests(config: {
         expect(getResExpire).toEqual(null);
         const setResWithExpiryWithUmilli = await client.set(key, value, {
             expiry: {
-                type: "unixMilliseconds",
+                type: TimeUnit.UnixMilliseconds,
                 count: Date.now() + 1000,
             },
         });
@@ -4892,7 +6281,7 @@ export function runBaseTests(config: {
         // * returns the old value
         const setResWithAllOptions = await client.set(key, value, {
             expiry: {
-                type: "unixSeconds",
+                type: TimeUnit.UnixSeconds,
                 count: Math.floor(Date.now() / 1000) + 1,
             },
             conditionalSet: "onlyIfExists",
@@ -4909,10 +6298,10 @@ export function runBaseTests(config: {
         const value = uuidv4();
         const count = 2;
         const expiryCombination = [
-            { type: "seconds", count },
-            { type: "unixSeconds", count },
-            { type: "unixMilliseconds", count },
-            { type: "milliseconds", count },
+            { type: TimeUnit.Seconds, count },
+            { type: TimeUnit.Milliseconds, count },
+            { type: TimeUnit.UnixSeconds, count },
+            { type: TimeUnit.UnixMilliseconds, count },
             "keepExisting",
         ];
         let exist = false;
@@ -4923,10 +6312,10 @@ export function runBaseTests(config: {
                     | "keepExisting"
                     | {
                           type:
-                              | "seconds"
-                              | "milliseconds"
-                              | "unixSeconds"
-                              | "unixMilliseconds";
+                              | TimeUnit.Seconds
+                              | TimeUnit.Milliseconds
+                              | TimeUnit.UnixSeconds
+                              | TimeUnit.UnixMilliseconds;
                           count: number;
                       },
                 conditionalSet: "onlyIfDoesNotExist",
@@ -4949,10 +6338,10 @@ export function runBaseTests(config: {
                     | "keepExisting"
                     | {
                           type:
-                              | "seconds"
-                              | "milliseconds"
-                              | "unixSeconds"
-                              | "unixMilliseconds";
+                              | TimeUnit.Seconds
+                              | TimeUnit.Milliseconds
+                              | TimeUnit.UnixSeconds
+                              | TimeUnit.UnixMilliseconds;
                           count: number;
                       },
 
@@ -5603,12 +6992,14 @@ export function runBaseTests(config: {
     );
 
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
-        `geosearch test_%p`,
+        `geosearch geosearchstore test_%p`,
         async (protocol) => {
             await runTest(async (client: BaseClient, cluster) => {
                 if (cluster.checkIfServerVersionLessThan("6.2.0")) return;
 
-                const key = uuidv4();
+                const key1 = "{geosearch}" + uuidv4();
+                const key2 = "{geosearch}" + uuidv4();
+                const key3 = "{geosearch}" + uuidv4();
 
                 const members: string[] = [
                     "Catania",
@@ -5672,30 +7063,55 @@ export function runBaseTests(config: {
                 ];
 
                 // geoadd
-                expect(await client.geoadd(key, membersToCoordinates)).toBe(
+                expect(await client.geoadd(key1, membersToCoordinates)).toBe(
                     members.length,
                 );
 
                 let searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { position: { longitude: 15, latitude: 37 } },
                     { width: 400, height: 400, unit: GeoUnit.KILOMETERS },
                 );
                 // using set to compare, because results are reordrered
                 expect(new Set(searchResult)).toEqual(membersSet);
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { position: { longitude: 15, latitude: 37 } },
+                        { width: 400, height: 400, unit: GeoUnit.KILOMETERS },
+                    ),
+                ).toEqual(4);
+                expect(
+                    await client.zrange(key2, { start: 0, stop: -1 }),
+                ).toEqual(searchResult);
 
                 // order search result
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { position: { longitude: 15, latitude: 37 } },
                     { width: 400, height: 400, unit: GeoUnit.KILOMETERS },
                     { sortOrder: SortOrder.ASC },
                 );
                 expect(searchResult).toEqual(members);
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { position: { longitude: 15, latitude: 37 } },
+                        { width: 400, height: 400, unit: GeoUnit.KILOMETERS },
+                        { sortOrder: SortOrder.ASC, storeDist: true },
+                    ),
+                ).toEqual(4);
+                expect(
+                    await client.zrange(key2, { start: 0, stop: -1 }),
+                ).toEqual(searchResult);
 
                 // order and query all extra data
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { position: { longitude: 15, latitude: 37 } },
                     { width: 400, height: 400, unit: GeoUnit.KILOMETERS },
                     {
@@ -5709,7 +7125,7 @@ export function runBaseTests(config: {
 
                 // order, query and limit by 1
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { position: { longitude: 15, latitude: 37 } },
                     { width: 400, height: 400, unit: GeoUnit.KILOMETERS },
                     {
@@ -5721,11 +7137,28 @@ export function runBaseTests(config: {
                     },
                 );
                 expect(searchResult).toEqual(expectedResult.slice(0, 1));
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { position: { longitude: 15, latitude: 37 } },
+                        { width: 400, height: 400, unit: GeoUnit.KILOMETERS },
+                        {
+                            sortOrder: SortOrder.ASC,
+                            count: 1,
+                            storeDist: true,
+                        },
+                    ),
+                ).toEqual(1);
+                expect(
+                    await client.zrange(key2, { start: 0, stop: -1 }),
+                ).toEqual([members[0]]);
 
                 // test search by box, unit: meters, from member, with distance
                 const meters = 400 * 1000;
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { member: "Catania" },
                     { width: meters, height: meters, unit: GeoUnit.METERS },
                     {
@@ -5739,11 +7172,33 @@ export function runBaseTests(config: {
                     ["Palermo", [166274.1516]],
                     ["Catania", [0.0]],
                 ]);
-
-                // test search by box, unit: feet, from member, with limited count 2, with hash
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { member: "Catania" },
+                        { width: meters, height: meters, unit: GeoUnit.METERS },
+                        { sortOrder: SortOrder.DESC, storeDist: true },
+                    ),
+                ).toEqual(3);
+                // TODO deep close to https://github.com/maasencioh/jest-matcher-deep-close-to
+                expect(
+                    await client.zrangeWithScores(
+                        key2,
+                        { start: 0, stop: -1 },
+                        true,
+                    ),
+                ).toEqual({
+                    edge2: 236529.17986494553,
+                    Palermo: 166274.15156960033,
+                    Catania: 0.0,
+                });
+
+                // test search by box, unit: feet, from member, with limited count 2, with hash
                 const feet = 400 * 3280.8399;
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { member: "Palermo" },
                     { width: feet, height: feet, unit: GeoUnit.FEET },
                     {
@@ -5758,39 +7213,97 @@ export function runBaseTests(config: {
                     ["Palermo", [3479099956230698]],
                     ["edge1", [3479273021651468]],
                 ]);
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { member: "Palermo" },
+                        { width: feet, height: feet, unit: GeoUnit.FEET },
+                        {
+                            sortOrder: SortOrder.ASC,
+                            count: 2,
+                        },
+                    ),
+                ).toEqual(2);
+                expect(
+                    await client.zrangeWithScores(key2, { start: 0, stop: -1 }),
+                ).toEqual({
+                    Palermo: 3479099956230698,
+                    edge1: 3479273021651468,
+                });
 
                 // test search by box, unit: miles, from geospatial position, with limited ANY count to 1
                 const miles = 250;
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { position: { longitude: 15, latitude: 37 } },
                     { width: miles, height: miles, unit: GeoUnit.MILES },
                     { count: 1, isAny: true },
                 );
                 expect(members).toContainEqual(searchResult[0]);
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { position: { longitude: 15, latitude: 37 } },
+                        { width: miles, height: miles, unit: GeoUnit.MILES },
+                        { count: 1, isAny: true },
+                    ),
+                ).toEqual(1);
+                expect(
+                    await client.zrange(key2, { start: 0, stop: -1 }),
+                ).toEqual(searchResult);
 
                 // test search by radius, units: feet, from member
                 const feetRadius = 200 * 3280.8399;
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { member: "Catania" },
                     { radius: feetRadius, unit: GeoUnit.FEET },
                     { sortOrder: SortOrder.ASC },
                 );
                 expect(searchResult).toEqual(["Catania", "Palermo"]);
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { member: "Catania" },
+                        { radius: feetRadius, unit: GeoUnit.FEET },
+                        { sortOrder: SortOrder.ASC, storeDist: true },
+                    ),
+                ).toEqual(2);
+                expect(
+                    await client.zrange(key2, { start: 0, stop: -1 }),
+                ).toEqual(searchResult);
 
                 // Test search by radius, unit: meters, from member
                 const metersRadius = 200 * 1000;
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { member: "Catania" },
                     { radius: metersRadius, unit: GeoUnit.METERS },
                     { sortOrder: SortOrder.DESC },
                 );
                 expect(searchResult).toEqual(["Palermo", "Catania"]);
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { member: "Catania" },
+                        { radius: metersRadius, unit: GeoUnit.METERS },
+                        { sortOrder: SortOrder.DESC, storeDist: true },
+                    ),
+                ).toEqual(2);
+                expect(
+                    await client.zrange(key2, { start: 0, stop: -1 }, true),
+                ).toEqual(searchResult);
 
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { member: "Catania" },
                     { radius: metersRadius, unit: GeoUnit.METERS },
                     {
@@ -5805,7 +7318,7 @@ export function runBaseTests(config: {
 
                 // Test search by radius, unit: miles, from geospatial data
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { position: { longitude: 15, latitude: 37 } },
                     { radius: 175, unit: GeoUnit.MILES },
                     { sortOrder: SortOrder.DESC },
@@ -5816,10 +7329,23 @@ export function runBaseTests(config: {
                     "Palermo",
                     "Catania",
                 ]);
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { position: { longitude: 15, latitude: 37 } },
+                        { radius: 175, unit: GeoUnit.MILES },
+                        { sortOrder: SortOrder.DESC, storeDist: true },
+                    ),
+                ).toEqual(4);
+                expect(
+                    await client.zrange(key2, { start: 0, stop: -1 }, true),
+                ).toEqual(searchResult);
 
                 // Test search by radius, unit: kilometers, from a geospatial data, with limited count to 2
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { position: { longitude: 15, latitude: 37 } },
                     { radius: 200, unit: GeoUnit.KILOMETERS },
                     {
@@ -5831,10 +7357,27 @@ export function runBaseTests(config: {
                     },
                 );
                 expect(searchResult).toEqual(expectedResult.slice(0, 2));
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { position: { longitude: 15, latitude: 37 } },
+                        { radius: 200, unit: GeoUnit.KILOMETERS },
+                        {
+                            sortOrder: SortOrder.ASC,
+                            count: 2,
+                            storeDist: true,
+                        },
+                    ),
+                ).toEqual(2);
+                expect(
+                    await client.zrange(key2, { start: 0, stop: -1 }),
+                ).toEqual(members.slice(0, 2));
 
                 // Test search by radius, unit: kilometers, from a geospatial data, with limited ANY count to 1
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { position: { longitude: 15, latitude: 37 } },
                     { radius: 200, unit: GeoUnit.KILOMETERS },
                     {
@@ -5847,40 +7390,94 @@ export function runBaseTests(config: {
                     },
                 );
                 expect(members).toContainEqual(searchResult[0][0]);
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { position: { longitude: 15, latitude: 37 } },
+                        { radius: 200, unit: GeoUnit.KILOMETERS },
+                        {
+                            sortOrder: SortOrder.ASC,
+                            count: 1,
+                            isAny: true,
+                        },
+                    ),
+                ).toEqual(1);
+                expect(
+                    await client.zrange(key2, { start: 0, stop: -1 }),
+                ).toEqual([searchResult[0][0]]);
 
                 // no members within the area
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { position: { longitude: 15, latitude: 37 } },
                     { width: 50, height: 50, unit: GeoUnit.METERS },
                     { sortOrder: SortOrder.ASC },
                 );
                 expect(searchResult).toEqual([]);
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { position: { longitude: 15, latitude: 37 } },
+                        { width: 50, height: 50, unit: GeoUnit.METERS },
+                        { sortOrder: SortOrder.ASC },
+                    ),
+                ).toEqual(0);
+                expect(await client.zcard(key2)).toEqual(0);
 
                 // no members within the area
                 searchResult = await client.geosearch(
-                    key,
+                    key1,
                     { position: { longitude: 15, latitude: 37 } },
                     { radius: 5, unit: GeoUnit.METERS },
                     { sortOrder: SortOrder.ASC },
                 );
                 expect(searchResult).toEqual([]);
+                // same with geosearchstore
+                expect(
+                    await client.geosearchstore(
+                        key2,
+                        key1,
+                        { position: { longitude: 15, latitude: 37 } },
+                        { radius: 5, unit: GeoUnit.METERS },
+                        { sortOrder: SortOrder.ASC },
+                    ),
+                ).toEqual(0);
+                expect(await client.zcard(key2)).toEqual(0);
 
                 // member does not exist
                 await expect(
                     client.geosearch(
-                        key,
+                        key1,
+                        { member: "non-existing-member" },
+                        { radius: 100, unit: GeoUnit.METERS },
+                    ),
+                ).rejects.toThrow(RequestError);
+                await expect(
+                    client.geosearchstore(
+                        key2,
+                        key1,
                         { member: "non-existing-member" },
                         { radius: 100, unit: GeoUnit.METERS },
                     ),
                 ).rejects.toThrow(RequestError);
 
                 // key exists but holds a non-ZSET value
-                const key2 = uuidv4();
-                expect(await client.set(key2, uuidv4())).toEqual("OK");
+                expect(await client.set(key3, uuidv4())).toEqual("OK");
                 await expect(
                     client.geosearch(
+                        key3,
+                        { position: { longitude: 15, latitude: 37 } },
+                        { radius: 100, unit: GeoUnit.METERS },
+                    ),
+                ).rejects.toThrow(RequestError);
+                await expect(
+                    client.geosearchstore(
                         key2,
+                        key3,
                         { position: { longitude: 15, latitude: 37 } },
                         { radius: 100, unit: GeoUnit.METERS },
                     ),
@@ -6666,89 +8263,770 @@ export function runBaseTests(config: {
     );
 
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
-        `lmpop test_%p`,
+        `xinfoconsumers xinfo consumers %p`,
         async (protocol) => {
-            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
-                if (cluster.checkIfServerVersionLessThan("7.0.0")) {
-                    return;
-                }
+            await runTest(async (client: BaseClient, cluster) => {
+                const key = uuidv4();
+                const stringKey = uuidv4();
+                const groupName1 = uuidv4();
+                const consumer1 = uuidv4();
+                const consumer2 = uuidv4();
+                const streamId1 = "0-1";
+                const streamId2 = "0-2";
+                const streamId3 = "0-3";
+                const streamId4 = "0-4";
 
-                const key1 = "{key}" + uuidv4();
-                const key2 = "{key}" + uuidv4();
-                const nonListKey = uuidv4();
-                const singleKeyArray = [key1];
-                const multiKeyArray = [key2, key1];
-                const count = 1;
-                const lpushArgs = ["one", "two", "three", "four", "five"];
-                const expected = { [key1]: ["five"] };
-                const expected2 = { [key2]: ["one", "two"] };
+                expect(
+                    await client.xadd(
+                        key,
+                        [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                        { id: streamId1 },
+                    ),
+                ).toEqual(streamId1);
 
-                // nothing to be popped
                 expect(
-                    await client.lmpop(
-                        singleKeyArray,
-                        ListDirection.LEFT,
-                        count,
+                    await client.xadd(
+                        key,
+                        [
+                            ["entry2_field1", "entry2_value1"],
+                            ["entry2_field2", "entry2_value2"],
+                        ],
+                        { id: streamId2 },
                     ),
-                ).toBeNull();
+                ).toEqual(streamId2);
 
-                // pushing to the arrays to be popped
-                expect(await client.lpush(key1, lpushArgs)).toEqual(5);
-                expect(await client.lpush(key2, lpushArgs)).toEqual(5);
+                expect(
+                    await client.xadd(
+                        key,
+                        [["entry3_field1", "entry3_value1"]],
+                        { id: streamId3 },
+                    ),
+                ).toEqual(streamId3);
 
-                // checking correct result from popping
                 expect(
-                    await client.lmpop(singleKeyArray, ListDirection.LEFT),
-                ).toEqual(expected);
+                    await client.xgroupCreate(key, groupName1, "0-0"),
+                ).toEqual("OK");
+                expect(
+                    await client.customCommand([
+                        "XREADGROUP",
+                        "GROUP",
+                        groupName1,
+                        consumer1,
+                        "COUNT",
+                        "1",
+                        "STREAMS",
+                        key,
+                        ">",
+                    ]),
+                ).toEqual({
+                    [key]: {
+                        [streamId1]: [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                    },
+                });
+                // Sleep to ensure the idle time value and inactive time value returned by xinfo_consumers is > 0
+                await new Promise((resolve) => setTimeout(resolve, 2000));
+                let result = await client.xinfoConsumers(key, groupName1);
+                expect(result.length).toEqual(1);
+                expect(result[0].name).toEqual(consumer1);
+                expect(result[0].pending).toEqual(1);
+                expect(result[0].idle).toBeGreaterThan(0);
+
+                if (cluster.checkIfServerVersionLessThan("7.2.0")) {
+                    expect(result[0].inactive).toBeGreaterThan(0);
+                }
 
-                // popping multiple elements from the right
                 expect(
-                    await client.lmpop(multiKeyArray, ListDirection.RIGHT, 2),
-                ).toEqual(expected2);
+                    await client.xgroupCreateConsumer(
+                        key,
+                        groupName1,
+                        consumer2,
+                    ),
+                ).toBeTruthy();
+                expect(
+                    await client.customCommand([
+                        "XREADGROUP",
+                        "GROUP",
+                        groupName1,
+                        consumer2,
+                        "STREAMS",
+                        key,
+                        ">",
+                    ]),
+                ).toEqual({
+                    [key]: {
+                        [streamId2]: [
+                            ["entry2_field1", "entry2_value1"],
+                            ["entry2_field2", "entry2_value2"],
+                        ],
+                        [streamId3]: [["entry3_field1", "entry3_value1"]],
+                    },
+                });
 
-                // Key exists, but is not a set
-                expect(await client.set(nonListKey, "lmpop")).toBe("OK");
+                // Verify that xinfo_consumers contains info for 2 consumers now
+                result = await client.xinfoConsumers(key, groupName1);
+                expect(result.length).toEqual(2);
+
+                // key exists, but it is not a stream
+                expect(await client.set(stringKey, "foo")).toEqual("OK");
                 await expect(
-                    client.lmpop([nonListKey], ListDirection.RIGHT),
+                    client.xinfoConsumers(stringKey, "_"),
                 ).rejects.toThrow(RequestError);
+
+                // Passing a non-existing key raises an error
+                const key2 = uuidv4();
+                await expect(client.xinfoConsumers(key2, "_")).rejects.toThrow(
+                    RequestError,
+                );
+
+                expect(
+                    await client.xadd(key2, [["field", "value"]], {
+                        id: streamId4,
+                    }),
+                ).toEqual(streamId4);
+
+                // Passing a non-existing group raises an error
+                await expect(client.xinfoConsumers(key2, "_")).rejects.toThrow(
+                    RequestError,
+                );
+
+                expect(
+                    await client.xgroupCreate(key2, groupName1, "0-0"),
+                ).toEqual("OK");
+                expect(await client.xinfoConsumers(key2, groupName1)).toEqual(
+                    [],
+                );
             }, protocol);
         },
         config.timeout,
     );
 
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
-        `blmpop test_%p`,
+        `xinfogroups xinfo groups %p`,
         async (protocol) => {
-            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
-                if (cluster.checkIfServerVersionLessThan("7.0.0")) {
-                    return;
-                }
+            await runTest(async (client: BaseClient, cluster) => {
+                const key = uuidv4();
+                const stringKey = uuidv4();
+                const groupName1 = uuidv4();
+                const consumer1 = uuidv4();
+                const streamId1 = "0-1";
+                const streamId2 = "0-2";
+                const streamId3 = "0-3";
 
-                const key1 = "{key}" + uuidv4();
-                const key2 = "{key}" + uuidv4();
-                const nonListKey = uuidv4();
-                const singleKeyArray = [key1];
-                const multiKeyArray = [key2, key1];
-                const count = 1;
-                const lpushArgs = ["one", "two", "three", "four", "five"];
-                const expected = { [key1]: ["five"] };
-                const expected2 = { [key2]: ["one", "two"] };
+                expect(
+                    await client.xgroupCreate(key, groupName1, "0-0", {
+                        mkStream: true,
+                    }),
+                ).toEqual("OK");
+
+                // one empty group exists
+                expect(await client.xinfoGroups(key)).toEqual(
+                    cluster.checkIfServerVersionLessThan("7.0.0")
+                        ? [
+                              {
+                                  name: groupName1,
+                                  consumers: 0,
+                                  pending: 0,
+                                  "last-delivered-id": "0-0",
+                              },
+                          ]
+                        : [
+                              {
+                                  name: groupName1,
+                                  consumers: 0,
+                                  pending: 0,
+                                  "last-delivered-id": "0-0",
+                                  "entries-read": null,
+                                  lag: 0,
+                              },
+                          ],
+                );
 
-                // nothing to be popped
                 expect(
-                    await client.blmpop(
-                        singleKeyArray,
-                        ListDirection.LEFT,
-                        0.1,
-                        count,
+                    await client.xadd(
+                        key,
+                        [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                        { id: streamId1 },
                     ),
-                ).toBeNull();
-
-                // pushing to the arrays to be popped
-                expect(await client.lpush(key1, lpushArgs)).toEqual(5);
-                expect(await client.lpush(key2, lpushArgs)).toEqual(5);
+                ).toEqual(streamId1);
 
-                // checking correct result from popping
+                expect(
+                    await client.xadd(
+                        key,
+                        [
+                            ["entry2_field1", "entry2_value1"],
+                            ["entry2_field2", "entry2_value2"],
+                        ],
+                        { id: streamId2 },
+                    ),
+                ).toEqual(streamId2);
+
+                expect(
+                    await client.xadd(
+                        key,
+                        [["entry3_field1", "entry3_value1"]],
+                        { id: streamId3 },
+                    ),
+                ).toEqual(streamId3);
+
+                // same as previous check, bug lag = 3, there are 3 messages unread
+                expect(await client.xinfoGroups(key)).toEqual(
+                    cluster.checkIfServerVersionLessThan("7.0.0")
+                        ? [
+                              {
+                                  name: groupName1,
+                                  consumers: 0,
+                                  pending: 0,
+                                  "last-delivered-id": "0-0",
+                              },
+                          ]
+                        : [
+                              {
+                                  name: groupName1,
+                                  consumers: 0,
+                                  pending: 0,
+                                  "last-delivered-id": "0-0",
+                                  "entries-read": null,
+                                  lag: 3,
+                              },
+                          ],
+                );
+
+                expect(
+                    await client.customCommand([
+                        "XREADGROUP",
+                        "GROUP",
+                        groupName1,
+                        consumer1,
+                        "STREAMS",
+                        key,
+                        ">",
+                    ]),
+                ).toEqual({
+                    [key]: {
+                        [streamId1]: [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                        [streamId2]: [
+                            ["entry2_field1", "entry2_value1"],
+                            ["entry2_field2", "entry2_value2"],
+                        ],
+                        [streamId3]: [["entry3_field1", "entry3_value1"]],
+                    },
+                });
+                // after reading, `lag` is reset, and `pending`, consumer count and last ID are set
+                expect(await client.xinfoGroups(key)).toEqual(
+                    cluster.checkIfServerVersionLessThan("7.0.0")
+                        ? [
+                              {
+                                  name: groupName1,
+                                  consumers: 1,
+                                  pending: 3,
+                                  "last-delivered-id": streamId3,
+                              },
+                          ]
+                        : [
+                              {
+                                  name: groupName1,
+                                  consumers: 1,
+                                  pending: 3,
+                                  "last-delivered-id": streamId3,
+                                  "entries-read": 3,
+                                  lag: 0,
+                              },
+                          ],
+                );
+
+                expect(
+                    await client.customCommand([
+                        "XACK",
+                        key,
+                        groupName1,
+                        streamId1,
+                    ]),
+                ).toEqual(1);
+                // once message ack'ed, pending counter decreased
+                expect(await client.xinfoGroups(key)).toEqual(
+                    cluster.checkIfServerVersionLessThan("7.0.0")
+                        ? [
+                              {
+                                  name: groupName1,
+                                  consumers: 1,
+                                  pending: 2,
+                                  "last-delivered-id": streamId3,
+                              },
+                          ]
+                        : [
+                              {
+                                  name: groupName1,
+                                  consumers: 1,
+                                  pending: 2,
+                                  "last-delivered-id": streamId3,
+                                  "entries-read": 3,
+                                  lag: 0,
+                              },
+                          ],
+                );
+
+                // key exists, but it is not a stream
+                expect(await client.set(stringKey, "foo")).toEqual("OK");
+                await expect(client.xinfoGroups(stringKey)).rejects.toThrow(
+                    RequestError,
+                );
+
+                // Passing a non-existing key raises an error
+                const key2 = uuidv4();
+                await expect(client.xinfoGroups(key2)).rejects.toThrow(
+                    RequestError,
+                );
+                // create a second stream
+                await client.xadd(key2, [["a", "b"]]);
+                // no group yet exists
+                expect(await client.xinfoGroups(key2)).toEqual([]);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `xpending test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key = uuidv4();
+                const group = uuidv4();
+
+                expect(
+                    await client.xgroupCreate(key, group, "0", {
+                        mkStream: true,
+                    }),
+                ).toEqual("OK");
+                expect(
+                    await client.customCommand([
+                        "xgroup",
+                        "createconsumer",
+                        key,
+                        group,
+                        "consumer",
+                    ]),
+                ).toEqual(true);
+
+                expect(
+                    await client.xadd(
+                        key,
+                        [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                        { id: "0-1" },
+                    ),
+                ).toEqual("0-1");
+                expect(
+                    await client.xadd(
+                        key,
+                        [["entry2_field1", "entry2_value1"]],
+                        { id: "0-2" },
+                    ),
+                ).toEqual("0-2");
+
+                expect(
+                    await client.customCommand([
+                        "xreadgroup",
+                        "group",
+                        group,
+                        "consumer",
+                        "STREAMS",
+                        key,
+                        ">",
+                    ]),
+                ).toEqual({
+                    [key]: {
+                        "0-1": [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                        "0-2": [["entry2_field1", "entry2_value1"]],
+                    },
+                });
+
+                // wait to get some minIdleTime
+                await new Promise((resolve) => setTimeout(resolve, 500));
+
+                expect(await client.xpending(key, group)).toEqual([
+                    2,
+                    "0-1",
+                    "0-2",
+                    [["consumer", "2"]],
+                ]);
+
+                const result = await client.xpendingWithOptions(key, group, {
+                    start: InfBoundary.NegativeInfinity,
+                    end: InfBoundary.PositiveInfinity,
+                    count: 1,
+                    minIdleTime: 42,
+                });
+                result[0][2] = 0; // overwrite msec counter to avoid test flakyness
+                expect(result).toEqual([["0-1", "consumer", 0, 1]]);
+
+                // not existing consumer
+                expect(
+                    await client.xpendingWithOptions(key, group, {
+                        start: { value: "0-1", isInclusive: true },
+                        end: { value: "0-2", isInclusive: false },
+                        count: 12,
+                        consumer: "_",
+                    }),
+                ).toEqual([]);
+
+                // key exists, but it is not a stream
+                const stringKey = uuidv4();
+                expect(await client.set(stringKey, "foo")).toEqual("OK");
+                await expect(client.xpending(stringKey, "_")).rejects.toThrow(
+                    RequestError,
+                );
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `xclaim test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key = uuidv4();
+                const group = uuidv4();
+
+                expect(
+                    await client.xgroupCreate(key, group, "0", {
+                        mkStream: true,
+                    }),
+                ).toEqual("OK");
+                expect(
+                    await client.xgroupCreateConsumer(key, group, "consumer"),
+                ).toEqual(true);
+
+                expect(
+                    await client.xadd(
+                        key,
+                        [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                        { id: "0-1" },
+                    ),
+                ).toEqual("0-1");
+                expect(
+                    await client.xadd(
+                        key,
+                        [["entry2_field1", "entry2_value1"]],
+                        { id: "0-2" },
+                    ),
+                ).toEqual("0-2");
+
+                expect(
+                    await client.customCommand([
+                        "xreadgroup",
+                        "group",
+                        group,
+                        "consumer",
+                        "STREAMS",
+                        key,
+                        ">",
+                    ]),
+                ).toEqual({
+                    [key]: {
+                        "0-1": [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                        "0-2": [["entry2_field1", "entry2_value1"]],
+                    },
+                });
+
+                expect(
+                    await client.xclaim(key, group, "consumer", 0, ["0-1"]),
+                ).toEqual({
+                    "0-1": [
+                        ["entry1_field1", "entry1_value1"],
+                        ["entry1_field2", "entry1_value2"],
+                    ],
+                });
+                expect(
+                    await client.xclaimJustId(key, group, "consumer", 0, [
+                        "0-2",
+                    ]),
+                ).toEqual(["0-2"]);
+
+                // add one more entry
+                expect(
+                    await client.xadd(
+                        key,
+                        [["entry3_field1", "entry3_value1"]],
+                        { id: "0-3" },
+                    ),
+                ).toEqual("0-3");
+                // using force, we can xclaim the message without reading it
+                expect(
+                    await client.xclaimJustId(
+                        key,
+                        group,
+                        "consumer",
+                        0,
+                        ["0-3"],
+                        { isForce: true, retryCount: 99 },
+                    ),
+                ).toEqual(["0-3"]);
+
+                // incorrect IDs - response is empty
+                expect(
+                    await client.xclaim(key, group, "consumer", 0, ["000"]),
+                ).toEqual({});
+                expect(
+                    await client.xclaimJustId(key, group, "consumer", 0, [
+                        "000",
+                    ]),
+                ).toEqual([]);
+
+                // empty ID array
+                await expect(
+                    client.xclaim(key, group, "consumer", 0, []),
+                ).rejects.toThrow(RequestError);
+
+                // key exists, but it is not a stream
+                const stringKey = uuidv4();
+                expect(await client.set(stringKey, "foo")).toEqual("OK");
+                await expect(
+                    client.xclaim(stringKey, "_", "_", 0, ["_"]),
+                ).rejects.toThrow(RequestError);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `xautoclaim test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient, cluster) => {
+                const key = uuidv4();
+                const group = uuidv4();
+
+                expect(
+                    await client.xgroupCreate(key, group, "0", {
+                        mkStream: true,
+                    }),
+                ).toEqual("OK");
+                expect(
+                    await client.xgroupCreateConsumer(key, group, "consumer"),
+                ).toEqual(true);
+
+                expect(
+                    await client.xadd(
+                        key,
+                        [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                        { id: "0-1" },
+                    ),
+                ).toEqual("0-1");
+                expect(
+                    await client.xadd(
+                        key,
+                        [["entry2_field1", "entry2_value1"]],
+                        { id: "0-2" },
+                    ),
+                ).toEqual("0-2");
+
+                expect(
+                    await client.customCommand([
+                        "xreadgroup",
+                        "group",
+                        group,
+                        "consumer",
+                        "STREAMS",
+                        key,
+                        ">",
+                    ]),
+                ).toEqual({
+                    [key]: {
+                        "0-1": [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                        "0-2": [["entry2_field1", "entry2_value1"]],
+                    },
+                });
+
+                let result = await client.xautoclaim(
+                    key,
+                    group,
+                    "consumer",
+                    0,
+                    "0-0",
+                    1,
+                );
+                let expected: typeof result = [
+                    "0-2",
+                    {
+                        "0-1": [
+                            ["entry1_field1", "entry1_value1"],
+                            ["entry1_field2", "entry1_value2"],
+                        ],
+                    },
+                ];
+                if (!cluster.checkIfServerVersionLessThan("7.0.0"))
+                    expected.push([]);
+                expect(result).toEqual(expected);
+
+                let result2 = await client.xautoclaimJustId(
+                    key,
+                    group,
+                    "consumer",
+                    0,
+                    "0-0",
+                );
+                let expected2: typeof result2 = ["0-0", ["0-1", "0-2"]];
+                if (!cluster.checkIfServerVersionLessThan("7.0.0"))
+                    expected2.push([]);
+                expect(result2).toEqual(expected2);
+
+                // add one more entry
+                expect(
+                    await client.xadd(
+                        key,
+                        [["entry3_field1", "entry3_value1"]],
+                        { id: "0-3" },
+                    ),
+                ).toEqual("0-3");
+
+                // incorrect IDs - response is empty
+                result = await client.xautoclaim(
+                    key,
+                    group,
+                    "consumer",
+                    0,
+                    "5-0",
+                );
+                expected = ["0-0", {}];
+                if (!cluster.checkIfServerVersionLessThan("7.0.0"))
+                    expected.push([]);
+                expect(result).toEqual(expected);
+
+                result2 = await client.xautoclaimJustId(
+                    key,
+                    group,
+                    "consumer",
+                    0,
+                    "5-0",
+                );
+                expected2 = ["0-0", []];
+                if (!cluster.checkIfServerVersionLessThan("7.0.0"))
+                    expected2.push([]);
+                expect(result2).toEqual(expected2);
+
+                // key exists, but it is not a stream
+                const stringKey = uuidv4();
+                expect(await client.set(stringKey, "foo")).toEqual("OK");
+                await expect(
+                    client.xautoclaim(stringKey, "_", "_", 0, "_"),
+                ).rejects.toThrow(RequestError);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `lmpop test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
+                if (cluster.checkIfServerVersionLessThan("7.0.0")) {
+                    return;
+                }
+
+                const key1 = "{key}" + uuidv4();
+                const key2 = "{key}" + uuidv4();
+                const nonListKey = uuidv4();
+                const singleKeyArray = [key1];
+                const multiKeyArray = [key2, key1];
+                const count = 1;
+                const lpushArgs = ["one", "two", "three", "four", "five"];
+                const expected = { [key1]: ["five"] };
+                const expected2 = { [key2]: ["one", "two"] };
+
+                // nothing to be popped
+                expect(
+                    await client.lmpop(
+                        singleKeyArray,
+                        ListDirection.LEFT,
+                        count,
+                    ),
+                ).toBeNull();
+
+                // pushing to the arrays to be popped
+                expect(await client.lpush(key1, lpushArgs)).toEqual(5);
+                expect(await client.lpush(key2, lpushArgs)).toEqual(5);
+
+                // checking correct result from popping
+                expect(
+                    await client.lmpop(singleKeyArray, ListDirection.LEFT),
+                ).toEqual(expected);
+
+                // popping multiple elements from the right
+                expect(
+                    await client.lmpop(multiKeyArray, ListDirection.RIGHT, 2),
+                ).toEqual(expected2);
+
+                // Key exists, but is not a set
+                expect(await client.set(nonListKey, "lmpop")).toBe("OK");
+                await expect(
+                    client.lmpop([nonListKey], ListDirection.RIGHT),
+                ).rejects.toThrow(RequestError);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `blmpop test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
+                if (cluster.checkIfServerVersionLessThan("7.0.0")) {
+                    return;
+                }
+
+                const key1 = "{key}" + uuidv4();
+                const key2 = "{key}" + uuidv4();
+                const nonListKey = uuidv4();
+                const singleKeyArray = [key1];
+                const multiKeyArray = [key2, key1];
+                const count = 1;
+                const lpushArgs = ["one", "two", "three", "four", "five"];
+                const expected = { [key1]: ["five"] };
+                const expected2 = { [key2]: ["one", "two"] };
+
+                // nothing to be popped
+                expect(
+                    await client.blmpop(
+                        singleKeyArray,
+                        ListDirection.LEFT,
+                        0.1,
+                        count,
+                    ),
+                ).toBeNull();
+
+                // pushing to the arrays to be popped
+                expect(await client.lpush(key1, lpushArgs)).toEqual(5);
+                expect(await client.lpush(key2, lpushArgs)).toEqual(5);
+
+                // checking correct result from popping
                 expect(
                     await client.blmpop(
                         singleKeyArray,
@@ -6777,6 +9055,115 @@ export function runBaseTests(config: {
         config.timeout,
     );
 
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `xgroupCreateConsumer and xgroupDelConsumer test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient) => {
+                const key = uuidv4();
+                const nonExistentKey = uuidv4();
+                const stringKey = uuidv4();
+                const groupName = uuidv4();
+                const consumer = uuidv4();
+                const streamId0 = "0";
+
+                // create group and consumer for the group
+                expect(
+                    await client.xgroupCreate(key, groupName, streamId0, {
+                        mkStream: true,
+                    }),
+                ).toEqual("OK");
+                expect(
+                    await client.xgroupCreateConsumer(key, groupName, consumer),
+                ).toEqual(true);
+
+                // attempting to create/delete a consumer for a group that does not exist results in a NOGROUP request error
+                await expect(
+                    client.xgroupCreateConsumer(
+                        key,
+                        "nonExistentGroup",
+                        consumer,
+                    ),
+                ).rejects.toThrow(RequestError);
+                await expect(
+                    client.xgroupDelConsumer(key, "nonExistentGroup", consumer),
+                ).rejects.toThrow(RequestError);
+
+                // attempt to create consumer for group again
+                expect(
+                    await client.xgroupCreateConsumer(key, groupName, consumer),
+                ).toEqual(false);
+
+                // attempting to delete a consumer that has not been created yet returns 0
+                expect(
+                    await client.xgroupDelConsumer(
+                        key,
+                        groupName,
+                        "nonExistentConsumer",
+                    ),
+                ).toEqual(0);
+
+                // Add two stream entries
+                const streamid1: string | null = await client.xadd(key, [
+                    ["field1", "value1"],
+                ]);
+                expect(streamid1).not.toBeNull();
+                const streamid2 = await client.xadd(key, [
+                    ["field2", "value2"],
+                ]);
+                expect(streamid2).not.toBeNull();
+
+                // read the entire stream for the consumer and mark messages as pending
+                expect(
+                    await client.customCommand([
+                        "XREADGROUP",
+                        "GROUP",
+                        groupName,
+                        consumer,
+                        "STREAMS",
+                        key,
+                        ">",
+                    ]),
+                ).toEqual({
+                    [key]: {
+                        [streamid1 as string]: [["field1", "value1"]],
+                        [streamid2 as string]: [["field2", "value2"]],
+                    },
+                });
+
+                // delete one of the streams
+                expect(
+                    await client.xgroupDelConsumer(key, groupName, consumer),
+                ).toEqual(2);
+
+                // attempting to call XGROUP CREATECONSUMER or XGROUP DELCONSUMER with a non-existing key should raise an error
+                await expect(
+                    client.xgroupCreateConsumer(
+                        nonExistentKey,
+                        groupName,
+                        consumer,
+                    ),
+                ).rejects.toThrow(RequestError);
+                await expect(
+                    client.xgroupDelConsumer(
+                        nonExistentKey,
+                        groupName,
+                        consumer,
+                    ),
+                ).rejects.toThrow(RequestError);
+
+                // key exists, but it is not a stream
+                expect(await client.set(stringKey, "foo")).toEqual("OK");
+                await expect(
+                    client.xgroupCreateConsumer(stringKey, groupName, consumer),
+                ).rejects.toThrow(RequestError);
+                await expect(
+                    client.xgroupDelConsumer(stringKey, groupName, consumer),
+                ).rejects.toThrow(RequestError);
+            }, protocol);
+        },
+        config.timeout,
+    );
+
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
         `xgroupCreate and xgroupDestroy test_%p`,
         async (protocol) => {
@@ -6863,6 +9250,51 @@ export function runBaseTests(config: {
         },
         config.timeout,
     );
+
+    it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
+        `getex test_%p`,
+        async (protocol) => {
+            await runTest(async (client: BaseClient, cluster: RedisCluster) => {
+                if (cluster.checkIfServerVersionLessThan("6.2.0")) {
+                    return;
+                }
+
+                const key1 = "{key}" + uuidv4();
+                const key2 = "{key}" + uuidv4();
+                const value = uuidv4();
+
+                expect(await client.set(key1, value)).toBe("OK");
+                expect(await client.getex(key1)).toEqual(value);
+                expect(await client.ttl(key1)).toBe(-1);
+
+                expect(
+                    await client.getex(key1, {
+                        type: TimeUnit.Seconds,
+                        duration: 15,
+                    }),
+                ).toEqual(value);
+                expect(await client.ttl(key1)).toBeGreaterThan(0);
+                expect(await client.getex(key1, "persist")).toEqual(value);
+                expect(await client.ttl(key1)).toBe(-1);
+
+                // non existent key
+                expect(await client.getex(key2)).toBeNull();
+
+                // invalid time measurement
+                await expect(
+                    client.getex(key1, {
+                        type: TimeUnit.Seconds,
+                        duration: -10,
+                    }),
+                ).rejects.toThrow(RequestError);
+
+                // Key exists, but is not a string
+                expect(await client.sadd(key2, ["a"])).toBe(1);
+                await expect(client.getex(key2)).rejects.toThrow(RequestError);
+            }, protocol);
+        },
+        config.timeout,
+    );
 }
 
 export function runCommonTests(config: {
diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts
index c1983f38cf..29147cdac4 100644
--- a/node/tests/TestUtilities.ts
+++ b/node/tests/TestUtilities.ts
@@ -19,11 +19,13 @@ import {
     ClusterTransaction,
     FlushMode,
     FunctionListResponse,
+    FunctionStatsResponse,
     GeoUnit,
     GeospatialData,
     GlideClient,
     GlideClusterClient,
-    InfScoreBoundary,
+    GlideString,
+    InfBoundary,
     InsertPosition,
     ListDirection,
     ProtocolVersion,
@@ -32,6 +34,7 @@ import {
     ScoreFilter,
     SignedEncoding,
     SortOrder,
+    TimeUnit,
     Transaction,
     UnsignedEncoding,
 } from "..";
@@ -128,8 +131,8 @@ export function checkSimple(left: any): Checker {
 }
 
 export type Client = {
-    set: (key: string, value: string) => Promise;
-    get: (key: string) => Promise;
+    set: (key: string, value: string) => Promise;
+    get: (key: string) => Promise;
 };
 
 export async function GetAndSetRandomValue(client: Client) {
@@ -253,6 +256,57 @@ export function generateLuaLibCode(
     return code;
 }
 
+/**
+ * Create a lua lib with a function which runs an endless loop up to timeout sec.
+ * Execution takes at least 5 sec regardless of the timeout configured.
+ */
+export function createLuaLibWithLongRunningFunction(
+    libName: string,
+    funcName: string,
+    timeout: number,
+    readOnly: boolean,
+): string {
+    const code =
+        "#!lua name=$libName\n" +
+        "local function $libName_$funcName(keys, args)\n" +
+        "  local started = tonumber(redis.pcall('time')[1])\n" +
+        // fun fact - redis does no write if 'no-writes' flag is set
+        "  redis.pcall('set', keys[1], 42)\n" +
+        "  while (true) do\n" +
+        "    local now = tonumber(redis.pcall('time')[1])\n" +
+        "    if now > started + $timeout then\n" +
+        "      return 'Timed out $timeout sec'\n" +
+        "    end\n" +
+        "  end\n" +
+        "  return 'OK'\n" +
+        "end\n" +
+        "redis.register_function{\n" +
+        "function_name='$funcName',\n" +
+        "callback=$libName_$funcName,\n" +
+        (readOnly ? "flags={ 'no-writes' }\n" : "") +
+        "}";
+    return code
+        .replaceAll("$timeout", timeout.toString())
+        .replaceAll("$funcName", funcName)
+        .replaceAll("$libName", libName);
+}
+
+export async function waitForNotBusy(client: GlideClusterClient | GlideClient) {
+    // If function wasn't killed, and it didn't time out - it blocks the server and cause rest test to fail.
+    let isBusy = true;
+
+    do {
+        try {
+            await client.functionKill();
+        } catch (err) {
+            // should throw `notbusy` error, because the function should be killed before
+            if ((err as Error).message.toLowerCase().includes("notbusy")) {
+                isBusy = false;
+            }
+        }
+    } while (isBusy);
+}
+
 /**
  * Parses the command-line arguments passed to the Node.js process.
  *
@@ -396,6 +450,43 @@ export function checkFunctionListResponse(
     expect(hasLib).toBeTruthy();
 }
 
+/**
+ * Validate whether `FUNCTION STATS` response contains required info.
+ *
+ * @param response - The response from server.
+ * @param runningFunction - Command line of running function expected. Empty, if nothing expected.
+ * @param libCount - Expected libraries count.
+ * @param functionCount - Expected functions count.
+ */
+export function checkFunctionStatsResponse(
+    response: FunctionStatsResponse,
+    runningFunction: string[],
+    libCount: number,
+    functionCount: number,
+) {
+    if (response.running_script === null && runningFunction.length > 0) {
+        fail("No running function info");
+    }
+
+    if (response.running_script !== null && runningFunction.length == 0) {
+        fail(
+            "Unexpected running function info: " +
+                (response.running_script.command as string[]).join(" "),
+        );
+    }
+
+    if (response.running_script !== null) {
+        expect(response.running_script.command).toEqual(runningFunction);
+        // command line format is:
+        // fcall|fcall_ro   * *
+        expect(response.running_script.name).toEqual(runningFunction[1]);
+    }
+
+    expect(response.engines).toEqual({
+        LUA: { libraries_count: libCount, functions_count: functionCount },
+    });
+}
+
 /**
  * Check transaction response.
  * @param response - Transaction result received from `exec` call.
@@ -439,6 +530,76 @@ export function validateTransactionResponse(
     }
 }
 
+/**
+ * Populates a transaction with commands to test the decodable commands with various default decoders.
+ * @param baseTransaction - A transaction.
+ * @param valueEncodedResponse - Represents the encoded response of "value" to compare
+ * @returns Array of tuples, where first element is a test name/description, second - expected return value.
+ */
+export async function encodableTransactionTest(
+    baseTransaction: Transaction | ClusterTransaction,
+    valueEncodedResponse: ReturnType,
+): Promise<[string, ReturnType][]> {
+    const key = "{key}" + uuidv4(); // string
+    const value = "value";
+    // array of tuples - first element is test name/description, second - expected return value
+    const responseData: [string, ReturnType][] = [];
+
+    baseTransaction.set(key, value);
+    responseData.push(["set(key, value)", "OK"]);
+    baseTransaction.get(key);
+    responseData.push(["get(key)", valueEncodedResponse]);
+
+    return responseData;
+}
+
+/**
+ * Populates a transaction with commands to test the decoded response.
+ * @param baseTransaction - A transaction.
+ * @returns Array of tuples, where first element is a test name/description, second - expected return value.
+ */
+export async function encodedTransactionTest(
+    baseTransaction: Transaction | ClusterTransaction,
+): Promise<[string, ReturnType][]> {
+    const key1 = "{key}" + uuidv4(); // string
+    const key2 = "{key}" + uuidv4(); // string
+    const key = "dumpKey";
+    const dumpResult = Buffer.from([
+        0, 5, 118, 97, 108, 117, 101, 11, 0, 232, 41, 124, 75, 60, 53, 114, 231,
+    ]);
+    const value = "value";
+    const valueEncoded = Buffer.from(value);
+    // array of tuples - first element is test name/description, second - expected return value
+    const responseData: [string, ReturnType][] = [];
+
+    baseTransaction.set(key1, value);
+    responseData.push(["set(key1, value)", "OK"]);
+    baseTransaction.set(key2, value);
+    responseData.push(["set(key2, value)", "OK"]);
+    baseTransaction.get(key1);
+    responseData.push(["get(key1)", valueEncoded]);
+    baseTransaction.get(key2);
+    responseData.push(["get(key2)", valueEncoded]);
+
+    baseTransaction.set(key, value);
+    responseData.push(["set(key, value)", "OK"]);
+    baseTransaction.customCommand(["DUMP", key]);
+    responseData.push(['customCommand(["DUMP", key])', dumpResult]);
+    baseTransaction.del([key]);
+    responseData.push(["del(key)", 1]);
+    baseTransaction.get(key);
+    responseData.push(["get(key)", null]);
+    baseTransaction.customCommand(["RESTORE", key, "0", dumpResult]);
+    responseData.push([
+        'customCommand(["RESTORE", key, "0", dumpResult])',
+        "OK",
+    ]);
+    baseTransaction.get(key);
+    responseData.push(["get(key)", valueEncoded]);
+
+    return responseData;
+}
+
 /**
  * Populates a transaction with commands to test.
  * @param baseTransaction - A transaction.
@@ -451,7 +612,7 @@ export async function transactionTest(
     const key1 = "{key}" + uuidv4(); // string
     const key2 = "{key}" + uuidv4(); // string
     const key3 = "{key}" + uuidv4(); // string
-    const key4 = "{key}" + uuidv4();
+    const key4 = "{key}" + uuidv4(); // hash
     const key5 = "{key}" + uuidv4();
     const key6 = "{key}" + uuidv4();
     const key7 = "{key}" + uuidv4();
@@ -472,10 +633,12 @@ export async function transactionTest(
     const key22 = "{key}" + uuidv4(); // list for sort
     const key23 = "{key}" + uuidv4(); // zset random
     const key24 = "{key}" + uuidv4(); // list value
+    const key25 = "{key}" + uuidv4(); // Geospatial Data/ZSET
     const field = uuidv4();
     const value = uuidv4();
     const groupName1 = uuidv4();
     const groupName2 = uuidv4();
+    const consumer = uuidv4();
     // array of tuples - first element is test name/description, second - expected return value
     const responseData: [string, ReturnType][] = [];
 
@@ -498,8 +661,21 @@ export async function transactionTest(
     responseData.push(["flushdb(FlushMode.SYNC)", "OK"]);
     baseTransaction.dbsize();
     responseData.push(["dbsize()", 0]);
-    baseTransaction.set(key1, "bar");
+    baseTransaction.set(key1, "foo");
     responseData.push(['set(key1, "bar")', "OK"]);
+    baseTransaction.set(key1, "bar", { returnOldValue: true });
+    responseData.push(['set(key1, "bar", {returnOldValue: true})', "foo"]);
+
+    if (gte(version, "6.2.0")) {
+        baseTransaction.getex(key1);
+        responseData.push(["getex(key1)", "bar"]);
+        baseTransaction.getex(key1, { type: TimeUnit.Seconds, duration: 1 });
+        responseData.push([
+            'getex(key1, {expiry: { type: "seconds", count: 1 }})',
+            "bar",
+        ]);
+    }
+
     baseTransaction.randomKey();
     responseData.push(["randomKey()", key1]);
     baseTransaction.getrange(key1, 0, -1);
@@ -538,19 +714,38 @@ export async function transactionTest(
     baseTransaction.strlen(key1);
     responseData.push(["strlen(key1)", 3]);
     baseTransaction.setrange(key1, 0, "GLIDE");
-    responseData.push(["setrange(key1, 0, 'GLIDE'", 5]);
+    responseData.push(["setrange(key1, 0, 'GLIDE')", 5]);
+    baseTransaction.del([key1]);
+    responseData.push(["del([key1])", 1]);
+    baseTransaction.append(key1, "bar");
+    responseData.push(["append(key1, value)", 3]);
     baseTransaction.del([key1]);
     responseData.push(["del([key1])", 1]);
     baseTransaction.hset(key4, { [field]: value });
     responseData.push(["hset(key4, { [field]: value })", 1]);
+    baseTransaction.hscan(key4, "0");
+    responseData.push(['hscan(key4, "0")', ["0", [field, value]]]);
+    baseTransaction.hscan(key4, "0", { match: "*", count: 20 });
+    responseData.push([
+        'hscan(key4, "0", {match: "*", count: 20})',
+        ["0", [field, value]],
+    ]);
     baseTransaction.hstrlen(key4, field);
     responseData.push(["hstrlen(key4, field)", value.length]);
     baseTransaction.hlen(key4);
     responseData.push(["hlen(key4)", 1]);
+    baseTransaction.hrandfield(key4);
+    responseData.push(["hrandfield(key4)", field]);
+    baseTransaction.hrandfieldCount(key4, -2);
+    responseData.push(["hrandfieldCount(key4, -2)", [field, field]]);
+    baseTransaction.hrandfieldWithValues(key4, 2);
+    responseData.push(["hrandfieldWithValues(key4, 2)", [[field, value]]]);
     baseTransaction.hsetnx(key4, field, value);
     responseData.push(["hsetnx(key4, field, value)", false]);
     baseTransaction.hvals(key4);
     responseData.push(["hvals(key4)", [value]]);
+    baseTransaction.hkeys(key4);
+    responseData.push(["hkeys(key4)", [field]]);
     baseTransaction.hget(key4, field);
     responseData.push(["hget(key4, field)", value]);
     baseTransaction.hgetall(key4);
@@ -561,6 +756,9 @@ export async function transactionTest(
     responseData.push(["hmget(key4, [field])", [null]]);
     baseTransaction.hexists(key4, field);
     responseData.push(["hexists(key4, field)", false]);
+    baseTransaction.hrandfield(key4);
+    responseData.push(["hrandfield(key4)", null]);
+
     baseTransaction.lpush(key5, [
         field + "1",
         field + "2",
@@ -685,6 +883,13 @@ export async function transactionTest(
     responseData.push(["sdiffstore(key7, [key7])", 2]);
     baseTransaction.srem(key7, ["foo"]);
     responseData.push(['srem(key7, ["foo"])', 1]);
+    baseTransaction.sscan(key7, "0");
+    responseData.push(['sscan(key7, "0")', ["0", ["bar"]]]);
+    baseTransaction.sscan(key7, "0", { match: "*", count: 20 });
+    responseData.push([
+        'sscan(key7, "0", {match: "*", count: 20})',
+        ["0", ["bar"]],
+    ]);
     baseTransaction.scard(key7);
     responseData.push(["scard(key7)", 1]);
     baseTransaction.sismember(key7, "bar");
@@ -700,6 +905,12 @@ export async function transactionTest(
 
     baseTransaction.smembers(key7);
     responseData.push(["smembers(key7)", new Set(["bar"])]);
+    baseTransaction.srandmember(key7);
+    responseData.push(["srandmember(key7)", "bar"]);
+    baseTransaction.srandmemberCount(key7, 2);
+    responseData.push(["srandmemberCount(key7, 2)", ["bar"]]);
+    baseTransaction.srandmemberCount(key7, -2);
+    responseData.push(["srandmemberCount(key7, -2)", ["bar", "bar"]]);
     baseTransaction.spop(key7);
     responseData.push(["spop(key7)", "bar"]);
     baseTransaction.spopCount(key7, 2);
@@ -766,6 +977,11 @@ export async function transactionTest(
     responseData.push(["zadd(key13, { one: 1, two: 2, three: 3.5 })", 3]);
 
     if (gte(version, "6.2.0")) {
+        baseTransaction.zrangeStore(key8, key8, { start: 0, stop: -1 });
+        responseData.push([
+            "zrangeStore(key8, key8, { start: 0, stop: -1 })",
+            4,
+        ]);
         baseTransaction.zdiff([key13, key12]);
         responseData.push(["zdiff([key13, key12])", ["three"]]);
         baseTransaction.zdiffWithScores([key13, key12]);
@@ -781,40 +997,44 @@ export async function transactionTest(
         responseData.push(["zinterstore(key12, [key12, key13])", 2]);
     }
 
-    baseTransaction.zcount(
-        key8,
-        { value: 2 },
-        InfScoreBoundary.PositiveInfinity,
-    );
+    baseTransaction.zcount(key8, { value: 2 }, InfBoundary.PositiveInfinity);
     responseData.push([
-        "zcount(key8, { value: 2 }, InfScoreBoundary.PositiveInfinity)",
+        "zcount(key8, { value: 2 }, InfBoundary.PositiveInfinity)",
         4,
     ]);
     baseTransaction.zlexcount(
         key8,
         { value: "a" },
-        InfScoreBoundary.PositiveInfinity,
+        InfBoundary.PositiveInfinity,
     );
     responseData.push([
-        'zlexcount(key8, { value: "a" }, InfScoreBoundary.PositiveInfinity)',
+        'zlexcount(key8, { value: "a" }, InfBoundary.PositiveInfinity)',
         4,
     ]);
     baseTransaction.zpopmin(key8);
     responseData.push(["zpopmin(key8)", { member2: 3.0 }]);
     baseTransaction.zpopmax(key8);
     responseData.push(["zpopmax(key8)", { member5: 5 }]);
+    baseTransaction.zadd(key8, { member6: 6 });
+    responseData.push(["zadd(key8, {member6: 6})", 1]);
+    baseTransaction.bzpopmax([key8], 0.5);
+    responseData.push(["bzpopmax([key8], 0.5)", [key8, "member6", 6]]);
+    baseTransaction.zadd(key8, { member7: 1 });
+    responseData.push(["zadd(key8, {member7: 1})", 1]);
+    baseTransaction.bzpopmin([key8], 0.5);
+    responseData.push(["bzpopmin([key8], 0.5)", [key8, "member7", 1]]);
     baseTransaction.zremRangeByRank(key8, 1, 1);
     responseData.push(["zremRangeByRank(key8, 1, 1)", 1]);
     baseTransaction.zremRangeByScore(
         key8,
-        InfScoreBoundary.NegativeInfinity,
-        InfScoreBoundary.PositiveInfinity,
+        InfBoundary.NegativeInfinity,
+        InfBoundary.PositiveInfinity,
     );
     responseData.push(["zremRangeByScore(key8, -Inf, +Inf)", 1]); // key8 is now empty
     baseTransaction.zremRangeByLex(
         key8,
-        InfScoreBoundary.NegativeInfinity,
-        InfScoreBoundary.PositiveInfinity,
+        InfBoundary.NegativeInfinity,
+        InfBoundary.PositiveInfinity,
     );
     responseData.push(["zremRangeByLex(key8, -Inf, +Inf)", 0]); // key8 is already empty
 
@@ -860,6 +1080,8 @@ export async function transactionTest(
     ]);
     baseTransaction.xlen(key9);
     responseData.push(["xlen(key9)", 3]);
+    baseTransaction.xrange(key9, { value: "0-1" }, { value: "0-1" });
+    responseData.push(["xrange(key9)", { "0-1": [["field", "value1"]] }]);
     baseTransaction.xread({ [key9]: "0-1" });
     responseData.push([
         'xread({ [key9]: "0-1" })',
@@ -879,6 +1101,8 @@ export async function transactionTest(
         'xtrim(key9, { method: "minid", threshold: "0-2", exact: true }',
         1,
     ]);
+    baseTransaction.xinfoGroups(key9);
+    responseData.push(["xinfoGroups(key9)", []]);
     baseTransaction.xgroupCreate(key9, groupName1, "0-0");
     responseData.push(['xgroupCreate(key9, groupName1, "0-0")', "OK"]);
     baseTransaction.xgroupCreate(key9, groupName2, "0-0", { mkStream: true });
@@ -886,12 +1110,96 @@ export async function transactionTest(
         'xgroupCreate(key9, groupName2, "0-0", { mkStream: true })',
         "OK",
     ]);
+    baseTransaction.xinfoConsumers(key9, groupName1);
+    responseData.push(["xinfoConsumers(key9, groupName1)", []]);
+    baseTransaction.xdel(key9, ["0-3", "0-5"]);
+    responseData.push(["xdel(key9, [['0-3', '0-5']])", 1]);
+
+    // key9 has one entry here: {"0-2":[["field","value2"]]}
+
+    baseTransaction.xgroupCreateConsumer(key9, groupName1, consumer);
+    responseData.push([
+        "xgroupCreateConsumer(key9, groupName1, consumer)",
+        true,
+    ]);
+    baseTransaction.customCommand([
+        "xreadgroup",
+        "group",
+        groupName1,
+        consumer,
+        "STREAMS",
+        key9,
+        ">",
+    ]);
+    responseData.push([
+        "xreadgroup(groupName1, consumer, key9, >)",
+        { [key9]: { "0-2": [["field", "value2"]] } },
+    ]);
+    baseTransaction.xpending(key9, groupName1);
+    responseData.push([
+        "xpending(key9, groupName1)",
+        [1, "0-2", "0-2", [[consumer, "1"]]],
+    ]);
+    baseTransaction.xpendingWithOptions(key9, groupName1, {
+        start: InfBoundary.NegativeInfinity,
+        end: InfBoundary.PositiveInfinity,
+        count: 10,
+    });
+    responseData.push([
+        "xpendingWithOptions(key9, groupName1, -, +, 10)",
+        [["0-2", consumer, 0, 1]],
+    ]);
+    baseTransaction.xclaim(key9, groupName1, consumer, 0, ["0-2"]);
+    responseData.push([
+        'xclaim(key9, groupName1, consumer, 0, ["0-2"])',
+        { "0-2": [["field", "value2"]] },
+    ]);
+    baseTransaction.xclaim(key9, groupName1, consumer, 0, ["0-2"], {
+        isForce: true,
+        retryCount: 0,
+        idle: 0,
+    });
+    responseData.push([
+        'xclaim(key9, groupName1, consumer, 0, ["0-2"], { isForce: true, retryCount: 0, idle: 0})',
+        { "0-2": [["field", "value2"]] },
+    ]);
+    baseTransaction.xclaimJustId(key9, groupName1, consumer, 0, ["0-2"]);
+    responseData.push([
+        'xclaimJustId(key9, groupName1, consumer, 0, ["0-2"])',
+        ["0-2"],
+    ]);
+    baseTransaction.xclaimJustId(key9, groupName1, consumer, 0, ["0-2"], {
+        isForce: true,
+        retryCount: 0,
+        idle: 0,
+    });
+    responseData.push([
+        'xclaimJustId(key9, groupName1, consumer, 0, ["0-2"], { isForce: true, retryCount: 0, idle: 0})',
+        ["0-2"],
+    ]);
+
+    if (gte(version, "6.2.0")) {
+        baseTransaction.xautoclaim(key9, groupName1, consumer, 0, "0-0", 1);
+        responseData.push([
+            'xautoclaim(key9, groupName1, consumer, 0, "0-0", 1)',
+            gte(version, "7.0.0")
+                ? ["0-0", { "0-2": [["field", "value2"]] }, []]
+                : ["0-0", { "0-2": [["field", "value2"]] }],
+        ]);
+        baseTransaction.xautoclaimJustId(key9, groupName1, consumer, 0, "0-0");
+        responseData.push([
+            'xautoclaimJustId(key9, groupName1, consumer, 0, "0-0")',
+            gte(version, "7.0.0") ? ["0-0", ["0-2"], []] : ["0-0", ["0-2"]],
+        ]);
+    }
+
+    baseTransaction.xgroupDelConsumer(key9, groupName1, consumer);
+    responseData.push(["xgroupDelConsumer(key9, groupName1, consumer)", 1]);
     baseTransaction.xgroupDestroy(key9, groupName1);
     responseData.push(["xgroupDestroy(key9, groupName1)", true]);
     baseTransaction.xgroupDestroy(key9, groupName2);
     responseData.push(["xgroupDestroy(key9, groupName2)", true]);
-    baseTransaction.xdel(key9, ["0-3", "0-5"]);
-    responseData.push(["xdel(key9, [['0-3', '0-5']])", 1]);
+
     baseTransaction.rename(key9, key10);
     responseData.push(["rename(key9, key10)", "OK"]);
     baseTransaction.exists([key10]);
@@ -925,7 +1233,7 @@ export async function transactionTest(
     baseTransaction.bitpos(key17, 1);
     responseData.push(["bitpos(key17, 1)", 1]);
 
-    if (gte("6.0.0", version)) {
+    if (gte(version, "6.0.0")) {
         baseTransaction.bitfieldReadOnly(key17, [
             new BitFieldGet(new SignedEncoding(5), new BitOffset(3)),
         ]);
@@ -1104,6 +1412,17 @@ export async function transactionTest(
                 ],
             ],
         ]);
+
+        baseTransaction.geosearchstore(
+            key25,
+            key18,
+            { position: { longitude: 15, latitude: 37 } },
+            { width: 400, height: 400, unit: GeoUnit.KILOMETERS },
+        );
+        responseData.push([
+            "geosearchstore(key25, key18, (15, 37), 400x400 KM)",
+            2,
+        ]);
     }
 
     const libName = "mylib1C" + uuidv4().replaceAll("-", "");
@@ -1115,6 +1434,8 @@ export async function transactionTest(
     );
 
     if (gte(version, "7.0.0")) {
+        baseTransaction.functionFlush();
+        responseData.push(["functionFlush()", "OK"]);
         baseTransaction.functionLoad(code);
         responseData.push(["functionLoad(code)", libName]);
         baseTransaction.functionLoad(code, true);
@@ -1128,6 +1449,14 @@ export async function transactionTest(
             'fcallReadonly(funcName, [], ["one", "two"]',
             "one",
         ]);
+        baseTransaction.functionStats();
+        responseData.push([
+            "functionStats()",
+            {
+                running_script: null,
+                engines: { LUA: { libraries_count: 1, functions_count: 1 } },
+            },
+        ]);
         baseTransaction.functionDelete(libName);
         responseData.push(["functionDelete(libName)", "OK"]);
         baseTransaction.functionFlush();
@@ -1213,5 +1542,7 @@ export async function transactionTest(
         responseData.push(["sortReadOnly(key21)", ["1", "2", "3"]]);
     }
 
+    baseTransaction.wait(1, 200);
+    responseData.push(["wait(1, 200)", 1]);
     return responseData;
 }
diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON
index 97ecdd3b01..98761ba6c4 100644
--- a/python/THIRD_PARTY_LICENSES_PYTHON
+++ b/python/THIRD_PARTY_LICENSES_PYTHON
@@ -4240,7 +4240,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: core-foundation-sys:0.8.6
+Package: core-foundation-sys:0.8.7
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -12569,7 +12569,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: js-sys:0.3.69
+Package: js-sys:0.3.70
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -14273,7 +14273,7 @@ the following restrictions:
 
 ----
 
-Package: mio:1.0.1
+Package: mio:1.0.2
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -15493,7 +15493,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: object:0.36.2
+Package: object:0.36.3
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -23754,7 +23754,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: serde:1.0.204
+Package: serde:1.0.207
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -23983,7 +23983,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: serde_derive:1.0.204
+Package: serde_derive:1.0.207
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -25086,7 +25086,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: syn:2.0.72
+Package: syn:2.0.74
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -29588,7 +29588,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: wasm-bindgen:0.2.92
+Package: wasm-bindgen:0.2.93
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -29817,7 +29817,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: wasm-bindgen-backend:0.2.92
+Package: wasm-bindgen-backend:0.2.93
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -30046,7 +30046,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: wasm-bindgen-macro:0.2.92
+Package: wasm-bindgen-macro:0.2.93
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -30275,7 +30275,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: wasm-bindgen-macro-support:0.2.92
+Package: wasm-bindgen-macro-support:0.2.93
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -30504,7 +30504,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ----
 
-Package: wasm-bindgen-shared:0.2.92
+Package: wasm-bindgen-shared:0.2.93
 
 The following copyrights and licenses were found in the source code of this package:
 
@@ -35832,7 +35832,7 @@ The following copyrights and licenses were found in the source code of this pack
 
 ----
 
-Package: google-auth:2.32.0
+Package: google-auth:2.33.0
 
 The following copyrights and licenses were found in the source code of this package:
 
diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py
index f59cb4662d..4371d23125 100644
--- a/python/python/glide/async_commands/cluster_commands.py
+++ b/python/python/glide/async_commands/cluster_commands.py
@@ -31,7 +31,7 @@
 class ClusterCommands(CoreCommands):
     async def custom_command(
         self, command_args: List[TEncodable], route: Optional[Route] = None
-    ) -> TResult:
+    ) -> TClusterResponse[TResult]:
         """
         Executes a single command, without checking inputs.
         See the [Valkey GLIDE Wiki](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command)
@@ -47,10 +47,11 @@ async def custom_command(
             case the client will route the command to the nodes defined by `route`. Defaults to None.
 
         Returns:
-            TResult: The returning value depends on the executed command and the route.
+            TClusterResponse[TResult]: The returning value depends on the executed command and the route.
         """
-        return await self._execute_command(
-            RequestType.CustomCommand, command_args, route
+        return cast(
+            TClusterResponse[TResult],
+            await self._execute_command(RequestType.CustomCommand, command_args, route),
         )
 
     async def info(
diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py
index c61f069a5f..d31a1a428c 100644
--- a/python/python/glide/async_commands/core.py
+++ b/python/python/glide/async_commands/core.py
@@ -6012,6 +6012,8 @@ async def restore(
 
         See https://valkey.io/commands/restore for more details.
 
+        Note: `IDLETIME` and `FREQ` modifiers cannot be set at the same time.
+
         Args:
             key (TEncodable): The `key` to create.
             ttl (int): The expiry time (in milliseconds). If `0`, the `key` will persist.
diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py
index 1573d51c03..dda2d58814 100644
--- a/python/python/glide/async_commands/transaction.py
+++ b/python/python/glide/async_commands/transaction.py
@@ -2108,6 +2108,8 @@ def restore(
 
         See https://valkey.io/commands/restore for more details.
 
+        Note: `IDLETIME` and `FREQ` modifiers cannot be set at the same time.
+
         Args:
             key (TEncodable): The `key` to create.
             ttl (int): The expiry time (in milliseconds). If `0`, the `key` will persist.