ioredis 5.9.3 → 5.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import * as EventEmitter from "events";
3
3
  import ShardedSubscriber from "./ShardedSubscriber";
4
+ import { ClusterOptions } from "./ClusterOptions";
4
5
  /**
5
6
  * Redis distinguishes between "normal" and sharded PubSub. When using the normal PubSub feature,
6
7
  * exactly one subscriber exists per cluster instance because the Redis cluster bus forwards
@@ -12,6 +13,7 @@ import ShardedSubscriber from "./ShardedSubscriber";
12
13
  */
13
14
  export default class ClusterSubscriberGroup {
14
15
  private readonly subscriberGroupEmitter;
16
+ private readonly options;
15
17
  private shardedSubscribers;
16
18
  private clusterSlots;
17
19
  private subscriberToSlotsIndex;
@@ -27,7 +29,7 @@ export default class ClusterSubscriberGroup {
27
29
  *
28
30
  * @param cluster
29
31
  */
30
- constructor(subscriberGroupEmitter: EventEmitter);
32
+ constructor(subscriberGroupEmitter: EventEmitter, options: ClusterOptions);
31
33
  /**
32
34
  * Get the responsible subscriber.
33
35
  *
@@ -102,4 +104,5 @@ export default class ClusterSubscriberGroup {
102
104
  * @param nodeKey
103
105
  */
104
106
  private handleSubscriberConnectSucceeded;
107
+ private shouldStartSubscriber;
105
108
  }
@@ -20,8 +20,9 @@ class ClusterSubscriberGroup {
20
20
  *
21
21
  * @param cluster
22
22
  */
23
- constructor(subscriberGroupEmitter) {
23
+ constructor(subscriberGroupEmitter, options) {
24
24
  this.subscriberGroupEmitter = subscriberGroupEmitter;
25
+ this.options = options;
25
26
  this.shardedSubscribers = new Map();
26
27
  this.clusterSlots = [];
27
28
  // Simple [min, max] slot ranges aren't enough because you can migrate single slots
@@ -68,7 +69,18 @@ class ClusterSubscriberGroup {
68
69
  */
69
70
  getResponsibleSubscriber(slot) {
70
71
  const nodeKey = this.clusterSlots[slot][0];
71
- return this.shardedSubscribers.get(nodeKey);
72
+ const sub = this.shardedSubscribers.get(nodeKey);
73
+ if (sub && sub.subscriberStatus === "idle") {
74
+ sub
75
+ .start()
76
+ .then(() => {
77
+ this.handleSubscriberConnectSucceeded(sub.getNodeKey());
78
+ })
79
+ .catch((err) => {
80
+ this.handleSubscriberConnectFailed(err, sub.getNodeKey());
81
+ });
82
+ }
83
+ return sub;
72
84
  }
73
85
  /**
74
86
  * Adds a channel for which this subscriber group is responsible
@@ -130,7 +142,7 @@ class ClusterSubscriberGroup {
130
142
  start() {
131
143
  const startPromises = [];
132
144
  for (const s of this.shardedSubscribers.values()) {
133
- if (!s.isStarted()) {
145
+ if (this.shouldStartSubscriber(s)) {
134
146
  startPromises.push(s
135
147
  .start()
136
148
  .then(() => {
@@ -139,6 +151,7 @@ class ClusterSubscriberGroup {
139
151
  .catch((err) => {
140
152
  this.handleSubscriberConnectFailed(err, s.getNodeKey());
141
153
  }));
154
+ this.subscriberGroupEmitter.emit("+subscriber");
142
155
  }
143
156
  }
144
157
  return Promise.all(startPromises);
@@ -162,9 +175,9 @@ class ClusterSubscriberGroup {
162
175
  // For each of the sharded subscribers
163
176
  for (const [nodeKey, shardedSubscriber] of this.shardedSubscribers) {
164
177
  if (
165
- // If the subscriber is still responsible for a slot range and is running then keep it
178
+ // If the subscriber is still responsible for a slot range and is healthy then keep it
166
179
  this.subscriberToSlotsIndex.has(nodeKey) &&
167
- shardedSubscriber.isStarted()) {
180
+ shardedSubscriber.isHealthy()) {
168
181
  debug("Skipping deleting subscriber for %s", nodeKey);
169
182
  continue;
170
183
  }
@@ -177,11 +190,31 @@ class ClusterSubscriberGroup {
177
190
  const startPromises = [];
178
191
  // For each node in slots cache
179
192
  for (const [nodeKey, _] of this.subscriberToSlotsIndex) {
180
- // If we already have a subscriber for this node then keep it
181
- if (this.shardedSubscribers.has(nodeKey)) {
193
+ const existingSubscriber = this.shardedSubscribers.get(nodeKey);
194
+ // If we already have a subscriber for this node, only ensure it is healthy
195
+ // when it now owns slots with active channel subscriptions.
196
+ if (existingSubscriber && existingSubscriber.isHealthy()) {
182
197
  debug("Skipping creating new subscriber for %s", nodeKey);
198
+ if (!existingSubscriber.isStarted() &&
199
+ this.shouldStartSubscriber(existingSubscriber)) {
200
+ startPromises.push(existingSubscriber
201
+ .start()
202
+ .then(() => {
203
+ this.handleSubscriberConnectSucceeded(nodeKey);
204
+ })
205
+ .catch((error) => {
206
+ this.handleSubscriberConnectFailed(error, nodeKey);
207
+ }));
208
+ }
183
209
  continue;
184
210
  }
211
+ // If we have an existing subscriber but it is not healthy, stop it
212
+ if (existingSubscriber && !existingSubscriber.isHealthy()) {
213
+ debug("Replacing subscriber for %s", nodeKey);
214
+ existingSubscriber.stop();
215
+ this.shardedSubscribers.delete(nodeKey);
216
+ this.subscriberGroupEmitter.emit("-subscriber");
217
+ }
185
218
  debug("Creating new subscriber for %s", nodeKey);
186
219
  // Otherwise create a new subscriber
187
220
  const redis = clusterNodes.find((node) => {
@@ -191,16 +224,18 @@ class ClusterSubscriberGroup {
191
224
  debug("Failed to find node for key %s", nodeKey);
192
225
  continue;
193
226
  }
194
- const sub = new ShardedSubscriber_1.default(this.subscriberGroupEmitter, redis.options);
227
+ const sub = new ShardedSubscriber_1.default(this.subscriberGroupEmitter, redis.options, this.options.redisOptions);
195
228
  this.shardedSubscribers.set(nodeKey, sub);
196
- startPromises.push(sub
197
- .start()
198
- .then(() => {
199
- this.handleSubscriberConnectSucceeded(nodeKey);
200
- })
201
- .catch((error) => {
202
- this.handleSubscriberConnectFailed(error, nodeKey);
203
- }));
229
+ if (this.shouldStartSubscriber(sub)) {
230
+ startPromises.push(sub
231
+ .start()
232
+ .then(() => {
233
+ this.handleSubscriberConnectSucceeded(nodeKey);
234
+ })
235
+ .catch((error) => {
236
+ this.handleSubscriberConnectFailed(error, nodeKey);
237
+ }));
238
+ }
204
239
  this.subscriberGroupEmitter.emit("+subscriber");
205
240
  }
206
241
  // It's vital to await the start promises before resubscribing
@@ -228,7 +263,8 @@ class ClusterSubscriberGroup {
228
263
  */
229
264
  _refreshSlots(targetSlots) {
230
265
  //If there was an actual change, then reassign the slot ranges
231
- if (this._slotsAreEqual(targetSlots)) {
266
+ // Also rebuild if subscriberToSlotsIndex is empty (e.g., after stop() was called)
267
+ if (this._slotsAreEqual(targetSlots) && this.subscriberToSlotsIndex.size > 0) {
232
268
  debug("Nothing to refresh because the new cluster map is equal to the previous one.");
233
269
  return false;
234
270
  }
@@ -262,7 +298,7 @@ class ClusterSubscriberGroup {
262
298
  const redis = s.getInstance();
263
299
  const channels = this.channels.get(ss);
264
300
  if (channels && channels.length > 0) {
265
- if (redis.status === "end") {
301
+ if (!redis || redis.status === "end") {
266
302
  return;
267
303
  }
268
304
  if (redis.status === "ready") {
@@ -309,10 +345,26 @@ class ClusterSubscriberGroup {
309
345
  * @returns true if any subscribers need to be recreated
310
346
  */
311
347
  hasUnhealthySubscribers() {
312
- const hasFailedSubscribers = Array.from(this.shardedSubscribers.values()).some((sub) => !sub.isStarted());
348
+ const hasFailedSubscribers = Array.from(this.shardedSubscribers.values()).some((sub) => !sub.isHealthy());
313
349
  const hasMissingSubscribers = Array.from(this.subscriberToSlotsIndex.keys()).some((nodeKey) => !this.shardedSubscribers.has(nodeKey));
314
350
  return hasFailedSubscribers || hasMissingSubscribers;
315
351
  }
352
+ shouldStartSubscriber(sub) {
353
+ if (sub.isStarted()) {
354
+ return false;
355
+ }
356
+ if (!sub.isLazyConnect()) {
357
+ return true;
358
+ }
359
+ const subscriberSlots = this.subscriberToSlotsIndex.get(sub.getNodeKey());
360
+ if (!subscriberSlots) {
361
+ return false;
362
+ }
363
+ return subscriberSlots.some((slot) => {
364
+ const channels = this.channels.get(slot);
365
+ return Boolean(channels && channels.length > 0);
366
+ });
367
+ }
316
368
  }
317
369
  exports.default = ClusterSubscriberGroup;
318
370
  // Retry strategy
@@ -2,19 +2,35 @@
2
2
  import EventEmitter = require("events");
3
3
  import { RedisOptions } from "./util";
4
4
  import Redis from "../Redis";
5
+ import { ClusterOptions } from "./ClusterOptions";
6
+ declare const SubscriberStatus: {
7
+ readonly IDLE: "idle";
8
+ readonly STARTING: "starting";
9
+ readonly CONNECTED: "connected";
10
+ readonly STOPPING: "stopping";
11
+ readonly ENDED: "ended";
12
+ };
13
+ declare type SubscriberStatus = typeof SubscriberStatus[keyof typeof SubscriberStatus];
5
14
  export default class ShardedSubscriber {
6
15
  private readonly emitter;
7
16
  private readonly nodeKey;
8
- private started;
17
+ private status;
9
18
  private instance;
19
+ private connectPromise;
20
+ private lazyConnect;
10
21
  private readonly messageListeners;
11
- constructor(emitter: EventEmitter, options: RedisOptions);
12
- private onEnd;
13
- private onError;
14
- private onMoved;
22
+ constructor(emitter: EventEmitter, options: RedisOptions, redisOptions?: ClusterOptions["redisOptions"]);
15
23
  start(): Promise<void>;
16
24
  stop(): void;
17
25
  isStarted(): boolean;
26
+ get subscriberStatus(): SubscriberStatus;
27
+ isHealthy(): boolean;
18
28
  getInstance(): Redis | null;
19
29
  getNodeKey(): string;
30
+ isLazyConnect(): boolean;
31
+ private onEnd;
32
+ private onError;
33
+ private onMoved;
34
+ private updateStatus;
20
35
  }
36
+ export {};
@@ -4,15 +4,42 @@ const util_1 = require("./util");
4
4
  const utils_1 = require("../utils");
5
5
  const Redis_1 = require("../Redis");
6
6
  const debug = (0, utils_1.Debug)("cluster:subscriberGroup:shardedSubscriber");
7
+ const SubscriberStatus = {
8
+ IDLE: "idle",
9
+ STARTING: "starting",
10
+ CONNECTED: "connected",
11
+ STOPPING: "stopping",
12
+ ENDED: "ended",
13
+ };
14
+ const ALLOWED_STATUS_UPDATES = {
15
+ [SubscriberStatus.IDLE]: [
16
+ SubscriberStatus.STARTING,
17
+ SubscriberStatus.STOPPING,
18
+ SubscriberStatus.ENDED,
19
+ ],
20
+ [SubscriberStatus.STARTING]: [
21
+ SubscriberStatus.CONNECTED,
22
+ SubscriberStatus.STOPPING,
23
+ SubscriberStatus.ENDED,
24
+ ],
25
+ [SubscriberStatus.CONNECTED]: [
26
+ SubscriberStatus.STOPPING,
27
+ SubscriberStatus.ENDED,
28
+ ],
29
+ [SubscriberStatus.STOPPING]: [SubscriberStatus.ENDED],
30
+ [SubscriberStatus.ENDED]: [],
31
+ };
7
32
  class ShardedSubscriber {
8
- constructor(emitter, options) {
33
+ constructor(emitter, options, redisOptions) {
34
+ var _a;
9
35
  this.emitter = emitter;
10
- this.started = false;
36
+ this.status = SubscriberStatus.IDLE;
11
37
  this.instance = null;
38
+ this.connectPromise = null;
12
39
  // Store listener references for cleanup
13
40
  this.messageListeners = new Map();
14
41
  this.onEnd = () => {
15
- this.started = false;
42
+ this.updateStatus(SubscriberStatus.ENDED);
16
43
  this.emitter.emit("-node", this.instance, this.nodeKey);
17
44
  };
18
45
  this.onError = (error) => {
@@ -21,25 +48,21 @@ class ShardedSubscriber {
21
48
  this.onMoved = () => {
22
49
  this.emitter.emit("moved");
23
50
  };
24
- this.instance = new Redis_1.default({
25
- port: options.port,
26
- host: options.host,
27
- username: options.username,
28
- password: options.password,
51
+ this.instance = new Redis_1.default((0, utils_1.defaults)({
29
52
  enableReadyCheck: false,
30
- offlineQueue: true,
53
+ enableOfflineQueue: true,
31
54
  connectionName: (0, util_1.getConnectionName)("ssubscriber", options.connectionName),
32
- lazyConnect: true,
33
- tls: options.tls,
34
55
  /**
35
56
  * Disable auto reconnection for subscribers.
36
57
  * The ClusterSubscriberGroup will handle the reconnection.
37
58
  */
38
59
  retryStrategy: null,
39
- });
60
+ lazyConnect: true,
61
+ }, options, redisOptions));
62
+ this.lazyConnect = (_a = redisOptions === null || redisOptions === void 0 ? void 0 : redisOptions.lazyConnect) !== null && _a !== void 0 ? _a : true;
40
63
  this.nodeKey = (0, util_1.getNodeKey)(options);
41
64
  // Register listeners
42
- this.instance.once("end", this.onEnd);
65
+ this.instance.on("end", this.onEnd);
43
66
  this.instance.on("error", this.onError);
44
67
  this.instance.on("moved", this.onMoved);
45
68
  for (const event of ["smessage", "smessageBuffer"]) {
@@ -51,33 +74,55 @@ class ShardedSubscriber {
51
74
  }
52
75
  }
53
76
  async start() {
54
- if (this.started) {
55
- debug("already started %s", this.nodeKey);
77
+ if (this.connectPromise) {
78
+ return this.connectPromise;
79
+ }
80
+ if (this.status === SubscriberStatus.STARTING ||
81
+ this.status === SubscriberStatus.CONNECTED) {
56
82
  return;
57
83
  }
84
+ if (this.status === SubscriberStatus.ENDED || !this.instance) {
85
+ throw new Error(`Sharded subscriber ${this.nodeKey} cannot be restarted once ended.`);
86
+ }
87
+ this.updateStatus(SubscriberStatus.STARTING);
88
+ this.connectPromise = this.instance.connect();
58
89
  try {
59
- await this.instance.connect();
60
- debug("started %s", this.nodeKey);
61
- this.started = true;
90
+ await this.connectPromise;
91
+ this.updateStatus(SubscriberStatus.CONNECTED);
62
92
  }
63
93
  catch (err) {
64
- debug("failed to start %s: %s", this.nodeKey, err);
65
- this.started = false;
66
- throw err; // Re-throw so caller knows it failed
94
+ this.updateStatus(SubscriberStatus.ENDED);
95
+ throw err;
96
+ }
97
+ finally {
98
+ this.connectPromise = null;
67
99
  }
68
100
  }
69
101
  stop() {
70
- this.started = false;
102
+ this.updateStatus(SubscriberStatus.STOPPING);
71
103
  if (this.instance) {
72
104
  this.instance.disconnect();
73
105
  this.instance.removeAllListeners();
74
106
  this.messageListeners.clear();
75
107
  this.instance = null;
76
108
  }
109
+ this.updateStatus(SubscriberStatus.ENDED);
77
110
  debug("stopped %s", this.nodeKey);
78
111
  }
79
112
  isStarted() {
80
- return this.started;
113
+ return [
114
+ SubscriberStatus.CONNECTED,
115
+ SubscriberStatus.STARTING,
116
+ ].includes(this.status);
117
+ }
118
+ get subscriberStatus() {
119
+ return this.status;
120
+ }
121
+ isHealthy() {
122
+ return ((this.status === SubscriberStatus.IDLE ||
123
+ this.status === SubscriberStatus.CONNECTED ||
124
+ this.status === SubscriberStatus.STARTING) &&
125
+ this.instance !== null);
81
126
  }
82
127
  getInstance() {
83
128
  return this.instance;
@@ -85,5 +130,18 @@ class ShardedSubscriber {
85
130
  getNodeKey() {
86
131
  return this.nodeKey;
87
132
  }
133
+ isLazyConnect() {
134
+ return this.lazyConnect;
135
+ }
136
+ updateStatus(nextStatus) {
137
+ if (this.status === nextStatus) {
138
+ return;
139
+ }
140
+ if (!ALLOWED_STATUS_UPDATES[this.status].includes(nextStatus)) {
141
+ debug("Invalid status transition for %s: %s -> %s", this.nodeKey, this.status, nextStatus);
142
+ return;
143
+ }
144
+ this.status = nextStatus;
145
+ }
88
146
  }
89
147
  exports.default = ShardedSubscriber;
@@ -889,7 +889,7 @@ class Cluster extends Commander_1.default {
889
889
  }
890
890
  createShardedSubscriberGroup() {
891
891
  this.subscriberGroupEmitter = new events_1.EventEmitter();
892
- this.shardedSubscribers = new ClusterSubscriberGroup_1.default(this.subscriberGroupEmitter);
892
+ this.shardedSubscribers = new ClusterSubscriberGroup_1.default(this.subscriberGroupEmitter, this.options);
893
893
  // Error handler used only for sharded-subscriber-triggered slots cache refreshes.
894
894
  // Normal (non-subscriber) connections are created with lazyConnect: true and can
895
895
  // become zombied. For sharded subscribers, a ClusterAllFailedError means
@@ -2263,6 +2263,30 @@ interface RedisCommander<Context extends ClientContext = {
2263
2263
  hexpire(...args: [key: RedisKey, seconds: number | string, gt: 'GT', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2264
2264
  hexpire(...args: [key: RedisKey, seconds: number | string, lt: 'LT', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2265
2265
  hexpire(...args: [key: RedisKey, seconds: number | string, lt: 'LT', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2266
+ /**
2267
+ * Set expiry for hash field using an absolute Unix timestamp (seconds)
2268
+ * - _group_: hash
2269
+ * - _complexity_: O(N) where N is the number of specified fields
2270
+ * - _since_: 7.4.0
2271
+ */
2272
+ hexpireat(...args: [key: RedisKey, unixTimeSeconds: number | string, fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2273
+ hexpireat(...args: [key: RedisKey, unixTimeSeconds: number | string, fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2274
+ hexpireat(...args: [key: RedisKey, unixTimeSeconds: number | string, nx: 'NX', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2275
+ hexpireat(...args: [key: RedisKey, unixTimeSeconds: number | string, nx: 'NX', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2276
+ hexpireat(...args: [key: RedisKey, unixTimeSeconds: number | string, xx: 'XX', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2277
+ hexpireat(...args: [key: RedisKey, unixTimeSeconds: number | string, xx: 'XX', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2278
+ hexpireat(...args: [key: RedisKey, unixTimeSeconds: number | string, gt: 'GT', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2279
+ hexpireat(...args: [key: RedisKey, unixTimeSeconds: number | string, gt: 'GT', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2280
+ hexpireat(...args: [key: RedisKey, unixTimeSeconds: number | string, lt: 'LT', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2281
+ hexpireat(...args: [key: RedisKey, unixTimeSeconds: number | string, lt: 'LT', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2282
+ /**
2283
+ * Returns the expiration time of a hash field as a Unix timestamp, in seconds.
2284
+ * - _group_: hash
2285
+ * - _complexity_: O(N) where N is the number of specified fields
2286
+ * - _since_: 7.4.0
2287
+ */
2288
+ hexpiretime(...args: [key: RedisKey, fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2289
+ hexpiretime(...args: [key: RedisKey, fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2266
2290
  /**
2267
2291
  * Get the value of a hash field
2268
2292
  * - _group_: hash
@@ -2279,6 +2303,46 @@ interface RedisCommander<Context extends ClientContext = {
2279
2303
  */
2280
2304
  hgetall(key: RedisKey, callback?: Callback<Record<string, string>>): Result<Record<string, string>, Context>;
2281
2305
  hgetallBuffer(key: RedisKey, callback?: Callback<Record<string, Buffer>>): Result<Record<string, Buffer>, Context>;
2306
+ /**
2307
+ * Returns the value of a field and deletes it from the hash.
2308
+ * - _group_: hash
2309
+ * - _complexity_: O(N) where N is the number of specified fields
2310
+ * - _since_: 8.0.0
2311
+ */
2312
+ hgetdel(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(string | null)[]>]): Result<(string | null)[], Context>;
2313
+ hgetdelBuffer(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(Buffer | null)[]>]): Result<(Buffer | null)[], Context>;
2314
+ hgetdel(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(string | null)[], Context>;
2315
+ hgetdelBuffer(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(Buffer | null)[], Context>;
2316
+ /**
2317
+ * Get the value of one or more fields of a given hash key, and optionally set their expiration.
2318
+ * - _group_: hash
2319
+ * - _complexity_: O(N) where N is the number of specified fields
2320
+ * - _since_: 8.0.0
2321
+ */
2322
+ hgetex(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(string | null)[]>]): Result<(string | null)[], Context>;
2323
+ hgetexBuffer(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(Buffer | null)[]>]): Result<(Buffer | null)[], Context>;
2324
+ hgetex(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(string | null)[], Context>;
2325
+ hgetexBuffer(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(Buffer | null)[], Context>;
2326
+ hgetex(...args: [key: RedisKey, secondsToken: "EX", seconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(string | null)[]>]): Result<(string | null)[], Context>;
2327
+ hgetexBuffer(...args: [key: RedisKey, secondsToken: "EX", seconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(Buffer | null)[]>]): Result<(Buffer | null)[], Context>;
2328
+ hgetex(...args: [key: RedisKey, secondsToken: "EX", seconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(string | null)[], Context>;
2329
+ hgetexBuffer(...args: [key: RedisKey, secondsToken: "EX", seconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(Buffer | null)[], Context>;
2330
+ hgetex(...args: [key: RedisKey, millisecondsToken: "PX", milliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(string | null)[]>]): Result<(string | null)[], Context>;
2331
+ hgetexBuffer(...args: [key: RedisKey, millisecondsToken: "PX", milliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(Buffer | null)[]>]): Result<(Buffer | null)[], Context>;
2332
+ hgetex(...args: [key: RedisKey, millisecondsToken: "PX", milliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(string | null)[], Context>;
2333
+ hgetexBuffer(...args: [key: RedisKey, millisecondsToken: "PX", milliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(Buffer | null)[], Context>;
2334
+ hgetex(...args: [key: RedisKey, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(string | null)[]>]): Result<(string | null)[], Context>;
2335
+ hgetexBuffer(...args: [key: RedisKey, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(Buffer | null)[]>]): Result<(Buffer | null)[], Context>;
2336
+ hgetex(...args: [key: RedisKey, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(string | null)[], Context>;
2337
+ hgetexBuffer(...args: [key: RedisKey, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(Buffer | null)[], Context>;
2338
+ hgetex(...args: [key: RedisKey, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(string | null)[]>]): Result<(string | null)[], Context>;
2339
+ hgetexBuffer(...args: [key: RedisKey, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(Buffer | null)[]>]): Result<(Buffer | null)[], Context>;
2340
+ hgetex(...args: [key: RedisKey, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(string | null)[], Context>;
2341
+ hgetexBuffer(...args: [key: RedisKey, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(Buffer | null)[], Context>;
2342
+ hgetex(...args: [key: RedisKey, persist: "PERSIST", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(string | null)[]>]): Result<(string | null)[], Context>;
2343
+ hgetexBuffer(...args: [key: RedisKey, persist: "PERSIST", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<(Buffer | null)[]>]): Result<(Buffer | null)[], Context>;
2344
+ hgetex(...args: [key: RedisKey, persist: "PERSIST", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(string | null)[], Context>;
2345
+ hgetexBuffer(...args: [key: RedisKey, persist: "PERSIST", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<(Buffer | null)[], Context>;
2282
2346
  /**
2283
2347
  * Increment the integer value of a hash field by the given number
2284
2348
  * - _group_: hash
@@ -2341,6 +2405,14 @@ interface RedisCommander<Context extends ClientContext = {
2341
2405
  callback: Callback<"OK">
2342
2406
  ]): Result<"OK", Context>;
2343
2407
  hmset(...args: [key: RedisKey, ...fieldValues: (string | Buffer | number)[]]): Result<"OK", Context>;
2408
+ /**
2409
+ * Removes the expiration time for each specified field
2410
+ * - _group_: hash
2411
+ * - _complexity_: O(N) where N is the number of specified fields
2412
+ * - _since_: 7.4.0
2413
+ */
2414
+ hpersist(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2415
+ hpersist(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2344
2416
  /**
2345
2417
  * Set expiry for hash field using relative time to expire (milliseconds)
2346
2418
  * - _group_: hash
@@ -2357,6 +2429,38 @@ interface RedisCommander<Context extends ClientContext = {
2357
2429
  hpexpire(...args: [key: RedisKey, milliseconds: number | string, gt: 'GT', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2358
2430
  hpexpire(...args: [key: RedisKey, milliseconds: number | string, lt: 'LT', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2359
2431
  hpexpire(...args: [key: RedisKey, milliseconds: number | string, lt: 'LT', fieldsToken: 'FIELDS', numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2432
+ /**
2433
+ * Set expiry for hash field using an absolute Unix timestamp (milliseconds)
2434
+ * - _group_: hash
2435
+ * - _complexity_: O(N) where N is the number of specified fields
2436
+ * - _since_: 7.4.0
2437
+ */
2438
+ hpexpireat(...args: [key: RedisKey, unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2439
+ hpexpireat(...args: [key: RedisKey, unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2440
+ hpexpireat(...args: [key: RedisKey, unixTimeMilliseconds: number | string, nx: "NX", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2441
+ hpexpireat(...args: [key: RedisKey, unixTimeMilliseconds: number | string, nx: "NX", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2442
+ hpexpireat(...args: [key: RedisKey, unixTimeMilliseconds: number | string, xx: "XX", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2443
+ hpexpireat(...args: [key: RedisKey, unixTimeMilliseconds: number | string, xx: "XX", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2444
+ hpexpireat(...args: [key: RedisKey, unixTimeMilliseconds: number | string, gt: "GT", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2445
+ hpexpireat(...args: [key: RedisKey, unixTimeMilliseconds: number | string, gt: "GT", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2446
+ hpexpireat(...args: [key: RedisKey, unixTimeMilliseconds: number | string, lt: "LT", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2447
+ hpexpireat(...args: [key: RedisKey, unixTimeMilliseconds: number | string, lt: "LT", fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2448
+ /**
2449
+ * Returns the expiration time of a hash field as a Unix timestamp, in msec.
2450
+ * - _group_: hash
2451
+ * - _complexity_: O(N) where N is the number of specified fields
2452
+ * - _since_: 7.4.0
2453
+ */
2454
+ hpexpiretime(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2455
+ hpexpiretime(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2456
+ /**
2457
+ * Returns the TTL in milliseconds of a hash field.
2458
+ * - _group_: hash
2459
+ * - _complexity_: O(N) where N is the number of specified fields
2460
+ * - _since_: 7.4.0
2461
+ */
2462
+ hpttl(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2463
+ hpttl(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2360
2464
  /**
2361
2465
  * Get one or multiple random fields from a hash
2362
2466
  * - _group_: hash
@@ -2397,6 +2501,48 @@ interface RedisCommander<Context extends ClientContext = {
2397
2501
  callback: Callback<number>
2398
2502
  ]): Result<number, Context>;
2399
2503
  hset(...args: [key: RedisKey, ...fieldValues: (string | Buffer | number)[]]): Result<number, Context>;
2504
+ /**
2505
+ * Set the value of one or more fields of a given hash key, and optionally set their expiration.
2506
+ * - _group_: hash
2507
+ * - _complexity_: O(N) where N is the number of fields being set.
2508
+ * - _since_: 8.0.0
2509
+ */
2510
+ hsetex(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2511
+ hsetex(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2512
+ hsetex(...args: [key: RedisKey, secondsToken: "EX", seconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2513
+ hsetex(...args: [key: RedisKey, secondsToken: "EX", seconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2514
+ hsetex(...args: [key: RedisKey, millisecondsToken: "PX", milliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2515
+ hsetex(...args: [key: RedisKey, millisecondsToken: "PX", milliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2516
+ hsetex(...args: [key: RedisKey, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2517
+ hsetex(...args: [key: RedisKey, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2518
+ hsetex(...args: [key: RedisKey, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2519
+ hsetex(...args: [key: RedisKey, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2520
+ hsetex(...args: [key: RedisKey, keepttl: "KEEPTTL", fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2521
+ hsetex(...args: [key: RedisKey, keepttl: "KEEPTTL", fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2522
+ hsetex(...args: [key: RedisKey, fnx: "FNX", fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2523
+ hsetex(...args: [key: RedisKey, fnx: "FNX", fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2524
+ hsetex(...args: [key: RedisKey, fnx: "FNX", secondsToken: "EX", seconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2525
+ hsetex(...args: [key: RedisKey, fnx: "FNX", secondsToken: "EX", seconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2526
+ hsetex(...args: [key: RedisKey, fnx: "FNX", millisecondsToken: "PX", milliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2527
+ hsetex(...args: [key: RedisKey, fnx: "FNX", millisecondsToken: "PX", milliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2528
+ hsetex(...args: [key: RedisKey, fnx: "FNX", unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2529
+ hsetex(...args: [key: RedisKey, fnx: "FNX", unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2530
+ hsetex(...args: [key: RedisKey, fnx: "FNX", unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2531
+ hsetex(...args: [key: RedisKey, fnx: "FNX", unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2532
+ hsetex(...args: [key: RedisKey, fnx: "FNX", keepttl: "KEEPTTL", fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2533
+ hsetex(...args: [key: RedisKey, fnx: "FNX", keepttl: "KEEPTTL", fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2534
+ hsetex(...args: [key: RedisKey, fxx: "FXX", fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2535
+ hsetex(...args: [key: RedisKey, fxx: "FXX", fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2536
+ hsetex(...args: [key: RedisKey, fxx: "FXX", secondsToken: "EX", seconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2537
+ hsetex(...args: [key: RedisKey, fxx: "FXX", secondsToken: "EX", seconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2538
+ hsetex(...args: [key: RedisKey, fxx: "FXX", millisecondsToken: "PX", milliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2539
+ hsetex(...args: [key: RedisKey, fxx: "FXX", millisecondsToken: "PX", milliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2540
+ hsetex(...args: [key: RedisKey, fxx: "FXX", unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2541
+ hsetex(...args: [key: RedisKey, fxx: "FXX", unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2542
+ hsetex(...args: [key: RedisKey, fxx: "FXX", unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2543
+ hsetex(...args: [key: RedisKey, fxx: "FXX", unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2544
+ hsetex(...args: [key: RedisKey, fxx: "FXX", keepttl: "KEEPTTL", fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[], callback: Callback<number>]): Result<number, Context>;
2545
+ hsetex(...args: [key: RedisKey, fxx: "FXX", keepttl: "KEEPTTL", fieldsToken: "FIELDS", numfields: number | string, ...data: (string | Buffer | number)[]]): Result<number, Context>;
2400
2546
  /**
2401
2547
  * Set the value of a hash field, only if the field does not exist
2402
2548
  * - _group_: hash
@@ -2411,6 +2557,14 @@ interface RedisCommander<Context extends ClientContext = {
2411
2557
  * - _since_: 3.2.0
2412
2558
  */
2413
2559
  hstrlen(key: RedisKey, field: string | Buffer, callback?: Callback<number>): Result<number, Context>;
2560
+ /**
2561
+ * Returns the TTL in seconds of a hash field.
2562
+ * - _group_: hash
2563
+ * - _complexity_: O(N) where N is the number of specified fields
2564
+ * - _since_: 7.4.0
2565
+ */
2566
+ httl(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[], callback: Callback<number[]>]): Result<number[], Context>;
2567
+ httl(...args: [key: RedisKey, fieldsToken: "FIELDS", numfields: number | string, ...fields: (string | Buffer)[]]): Result<number[], Context>;
2414
2568
  /**
2415
2569
  * Get all the values in a hash
2416
2570
  * - _group_: hash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ioredis",
3
- "version": "5.9.3",
3
+ "version": "5.10.1",
4
4
  "description": "A robust, performance-focused and full-featured Redis client for Node.js.",
5
5
  "main": "./built/index.js",
6
6
  "types": "./built/index.d.ts",
@@ -43,7 +43,7 @@
43
43
  "url": "https://opencollective.com/ioredis"
44
44
  },
45
45
  "dependencies": {
46
- "@ioredis/commands": "1.5.0",
46
+ "@ioredis/commands": "1.5.1",
47
47
  "cluster-key-slot": "^1.1.0",
48
48
  "debug": "^4.3.4",
49
49
  "denque": "^2.1.0",