To better align with Node.js build-in net
and tls
modules, the keepAlive
option has been split into 2 options: keepAlive
(boolean
) and keepAliveInitialDelay
(number
). The defaults remain true
and 5000
.
In the previous version, you could access "legacy" mode by creating a client and passing in { legacyMode: true }
. Now, you can create one off of an existing client by calling the .legacy()
function. This allows easier access to both APIs and enables better TypeScript support.
// use `client` for the current API
const client = createClient();
await client.set('key', 'value');
// use `legacyClient` for the "legacy" API
const legacyClient = client.legacy();
legacyClient.set('key', 'value', (err, reply) => {
// ...
});
In v4, command options are passed as a first optional argument:
await client.get('key'); // `string | null`
await client.get(client.commandOptions({ returnBuffers: true }), 'key'); // `Buffer | null`
This has a couple of flaws:
- The argument types are checked in runtime, which is a performance hit.
- Code suggestions are less readable/usable, due to "function overloading".
- Overall, "user code" is not as readable as it could be.
With the new API, instead of passing the options directly to the commands we use a "proxy client" to store them:
await client.get('key'); // `string | null`
const proxyClient = client.withCommandOptions({
typeMapping: {
[TYPES.BLOB_STRING]: Buffer
}
});
await proxyClient.get('key'); // `Buffer | null`
for more information, see the Command Options guide.
The QUIT
command has been deprecated in Redis 7.2 and should now also be considered deprecated in Node-Redis. Instead of sending a QUIT
command to the server, the client can simply close the network connection.
client.QUIT/quit()
is replaced by client.close()
. and, to avoid confusion, client.disconnect()
has been renamed to client.destroy()
.
Iterator commands like SCAN
, HSCAN
, SSCAN
, and ZSCAN
return collections of elements (depending on the data type). However, v4 iterators loop over these collections and yield individual items:
for await (const key of client.scanIterator()) {
console.log(key, await client.get(key));
}
This mismatch can be awkward and makes "multi-key" commands like MGET
, UNLINK
, etc. pointless. So, in v5 the iterators now yield a collection instead of an element:
for await (const keys of client.scanIterator()) {
// we can now meaningfully utilize "multi-key" commands
console.log(keys, await client.mGet(keys));
}
for more information, see the Scan Iterators guide.
In v4, RedisClient
had the ability to create a pool of connections using an "Isolation Pool" on top of the "main" connection. However, there was no way to use the pool without a "main" connection:
const client = await createClient()
.on('error', err => console.error(err))
.connect();
await client.ping(
client.commandOptions({ isolated: true })
);
In v5 we've extracted this pool logic into its own class—RedisClientPool
:
const pool = await createClientPool()
.on('error', err => console.error(err))
.connect();
await pool.ping();
See the pool guide for more information.
In v4, cluster.multi()
did not support executing commands on replicas, even if they were readonly.
// this might execute on a replica, depending on configuration
await cluster.sendCommand('key', true, ['GET', 'key']);
// this always executes on a master
await cluster.multi()
.addCommand('key', ['GET', 'key'])
.exec();
To support executing commands on replicas, cluster.multi().addCommand
now requires isReadonly
as the second argument, which matches the signature of cluster.sendCommand
:
await cluster.multi()
.addCommand('key', true, ['GET', 'key'])
.exec();
await client.multi()
.set('a', 'a')
.set('b', 'b')
.execAsPipeline();
In older versions, if the socket disconnects during the pipeline execution, i.e. after writing SET a a
and before SET b b
, the returned promise is rejected, but SET b b
will still be executed on the server.
In v5, any unwritten commands (in the same pipeline) will be discarded.
-
ACL GETUSER
:selectors
-
COPY
:destinationDb
->DB
,replace
->REPLACE
,boolean
->number
[^boolean-to-number] -
CLIENT KILL
:enum ClientKillFilters
->const CLIENT_KILL_FILTERS
1 -
CLUSTER FAILOVER
:enum FailoverModes
->const FAILOVER_MODES
1 -
CLIENT TRACKINGINFO
:flags
in RESP2 -Set<string>
->Array<string>
(to match RESP3 default type mapping) -
CLUSTER INFO
: -
CLUSTER SETSLOT
:ClusterSlotStates
->CLUSTER_SLOT_STATES
1 -
CLUSTER RESET
: the second argument is{ mode: string; }
instead ofstring
2 -
CLUSTER FAILOVER
:enum FailoverModes
->const FAILOVER_MODES
1, the second argument is{ mode: string; }
instead ofstring
2 -
CLUSTER LINKS
:createTime
->create-time
,sendBufferAllocated
->send-buffer-allocated
,sendBufferUsed
->send-buffer-used
3 -
CLUSTER NODES
,CLUSTER REPLICAS
,CLUSTER INFO
: returning the rawVerbatimStringReply
-
EXPIRE
:boolean
->number
[^boolean-to-number] -
EXPIREAT
:boolean
->number
[^boolean-to-number] -
HSCAN
:tuples
has been renamed toentries
-
HEXISTS
:boolean
->number
[^boolean-to-number] -
HRANDFIELD_COUNT_WITHVALUES
:Record<BlobString, BlobString>
->Array<{ field: BlobString; value: BlobString; }>
(it can return duplicates). -
HSETNX
:boolean
->number
[^boolean-to-number] -
INFO
: -
LCS IDX
:length
has been changed tolen
,matches
has been changed fromArray<{ key1: RangeReply; key2: RangeReply; }>
toArray<[key1: RangeReply, key2: RangeReply]>
-
ZINTER
: instead ofclient.ZINTER('key', { WEIGHTS: [1] })
useclient.ZINTER({ key: 'key', weight: 1 }])
-
ZINTER_WITHSCORES
: instead ofclient.ZINTER_WITHSCORES('key', { WEIGHTS: [1] })
useclient.ZINTER_WITHSCORES({ key: 'key', weight: 1 }])
-
ZUNION
: instead ofclient.ZUNION('key', { WEIGHTS: [1] })
useclient.ZUNION({ key: 'key', weight: 1 }])
-
ZUNION_WITHSCORES
: instead ofclient.ZUNION_WITHSCORES('key', { WEIGHTS: [1] })
useclient.ZUNION_WITHSCORES({ key: 'key', weight: 1 }])
-
ZMPOP
:{ elements: Array<{ member: string; score: number; }>; }
->{ members: Array<{ value: string; score: number; }>; }
to match other sorted set commands (e.g.ZRANGE
,ZSCAN
) -
MOVE
:boolean
->number
[^boolean-to-number] -
PEXPIRE
:boolean
->number
[^boolean-to-number] -
PEXPIREAT
:boolean
->number
[^boolean-to-number] -
PFADD
:boolean
->number
[^boolean-to-number] -
RENAMENX
:boolean
->number
[^boolean-to-number] -
SETNX
:boolean
->number
[^boolean-to-number] -
SCAN
,HSCAN
,SSCAN
, andZSCAN
:reply.cursor
will not be converted to number to avoid issues when the number is bigger thanNumber.MAX_SAFE_INTEGER
. See here. -
SCRIPT EXISTS
:Array<boolean>
->Array<number>
[^boolean-to-number] -
SISMEMBER
:boolean
->number
[^boolean-to-number] -
SMISMEMBER
:Array<boolean>
->Array<number>
[^boolean-to-number] -
SMOVE
:boolean
->number
[^boolean-to-number] -
GEOSEARCH_WITH
/GEORADIUS_WITH
:GeoReplyWith
->GEO_REPLY_WITH
1 -
GEORADIUSSTORE
->GEORADIUS_STORE
-
GEORADIUSBYMEMBERSTORE
->GEORADIUSBYMEMBER_STORE
-
XACK
:boolean
->number
[^boolean-to-number] -
XADD
: theINCR
option has been removed, useXADD_INCR
instead -
LASTSAVE
:Date
->number
(unix timestamp) -
HELLO
:protover
moved from the options object to it's own argument,auth
->AUTH
,clientName
->SETNAME
-
MODULE LIST
:version
->ver
3 -
MEMORY STATS
: 3 -
FUNCTION RESTORE
: the second argument is{ mode: string; }
instead ofstring
2 -
FUNCTION STATS
:runningScript
->running_script
,durationMs
->duration_ms
,librariesCount
->libraries_count
,functionsCount
->functions_count
3 -
TIME
:Date
->[unixTimestamp: string, microseconds: string]
-
XGROUP_CREATECONSUMER
: [^boolean-to-number] -
XGROUP_DESTROY
: [^boolean-to-number] -
XINFO GROUPS
:lastDeliveredId
->last-delivered-id
3 -
XINFO STREAM
:radixTreeKeys
->radix-tree-keys
,radixTreeNodes
->radix-tree-nodes
,lastGeneratedId
->last-generated-id
,maxDeletedEntryId
->max-deleted-entry-id
,entriesAdded
->entries-added
,recordedFirstEntryId
->recorded-first-entry-id
,firstEntry
->first-entry
,lastEntry
->last-entry
-
XAUTOCLAIM
,XCLAIM
,XRANGE
,XREVRANGE
:Array<{ name: string; messages: Array<{ id: string; message: Record<string, string> }>; }>
->Record<string, Array<{ id: string; message: Record<string, string> }>>
-
COMMAND LIST
:enum FilterBy
->const COMMAND_LIST_FILTER_BY
1, the filter argument has been moved from a "top level argument" into{ FILTERBY: { type: <MODULE|ACLCAT|PATTERN>; value: <value> } }
TOPK.QUERY
:Array<number>
->Array<boolean>
GRAPH.SLOWLOG
:timestamp
has been changed fromDate
tonumber
JSON.ARRINDEX
:start
andend
arguments moved to{ range: { start: number; end: number; }; }
2JSON.ARRPOP
:path
andindex
arguments moved to{ path: string; index: number; }
2JSON.ARRLEN
,JSON.CLEAR
,JSON.DEBUG MEMORY
,JSON.DEL
,JSON.FORGET
,JSON.OBJKEYS
,JSON.OBJLEN
,JSON.STRAPPEND
,JSON.STRLEN
,JSON.TYPE
:path
argument moved to{ path: string; }
2
FT.SUGDEL
: [^boolean-to-number]FT.CURSOR READ
:cursor
type changed fromnumber
tostring
(in and out) to avoid issues when the number is bigger thanNumber.MAX_SAFE_INTEGER
. See here.
TS.ADD
:boolean
->number
[^boolean-to-number]TS.[M][REV]RANGE
:enum TimeSeriesBucketTimestamp
->const TIME_SERIES_BUCKET_TIMESTAMP
1,enum TimeSeriesReducers
->const TIME_SERIES_REDUCERS
1, theALIGN
argument has been moved intoAGGREGRATION
TS.SYNUPDATE
:Array<string | Array<string>>
->Record<string, Array<string>>
TS.M[REV]RANGE[_WITHLABELS]
,TS.MGET[_WITHLABELS]
: TODO