@upstash/redis 1.35.0-canary → 1.35.0

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.
@@ -19,12 +19,57 @@ var UpstashError = class extends Error {
19
19
  var UrlError = class extends Error {
20
20
  constructor(url) {
21
21
  super(
22
- `Upstash Redis client was passed an invalid URL. You should pass the URL together with https. Received: "${url}". `
22
+ `Upstash Redis client was passed an invalid URL. You should pass a URL starting with https. Received: "${url}". `
23
23
  );
24
24
  this.name = "UrlError";
25
25
  }
26
26
  };
27
27
 
28
+ // pkg/util.ts
29
+ function parseRecursive(obj) {
30
+ const parsed = Array.isArray(obj) ? obj.map((o) => {
31
+ try {
32
+ return parseRecursive(o);
33
+ } catch {
34
+ return o;
35
+ }
36
+ }) : JSON.parse(obj);
37
+ if (typeof parsed === "number" && parsed.toString() !== obj) {
38
+ return obj;
39
+ }
40
+ return parsed;
41
+ }
42
+ function parseResponse(result) {
43
+ try {
44
+ return parseRecursive(result);
45
+ } catch {
46
+ return result;
47
+ }
48
+ }
49
+ function deserializeScanResponse(result) {
50
+ return [result[0], ...parseResponse(result.slice(1))];
51
+ }
52
+ function deserializeScanWithTypesResponse(result) {
53
+ const [cursor, keys] = result;
54
+ const parsedKeys = [];
55
+ for (let i = 0; i < keys.length; i += 2) {
56
+ parsedKeys.push({ key: keys[i], type: keys[i + 1] });
57
+ }
58
+ return [cursor, parsedKeys];
59
+ }
60
+ function mergeHeaders(...headers) {
61
+ const merged = {};
62
+ for (const header of headers) {
63
+ if (!header) continue;
64
+ for (const [key, value] of Object.entries(header)) {
65
+ if (value !== void 0 && value !== null) {
66
+ merged[key] = value;
67
+ }
68
+ }
69
+ }
70
+ return merged;
71
+ }
72
+
28
73
  // pkg/http.ts
29
74
  var HttpClient = class {
30
75
  baseUrl;
@@ -32,6 +77,7 @@ var HttpClient = class {
32
77
  options;
33
78
  readYourWrites;
34
79
  upstashSyncToken = "";
80
+ hasCredentials;
35
81
  retry;
36
82
  constructor(config) {
37
83
  this.options = {
@@ -45,15 +91,16 @@ var HttpClient = class {
45
91
  };
46
92
  this.upstashSyncToken = "";
47
93
  this.readYourWrites = config.readYourWrites ?? true;
48
- this.baseUrl = config.baseUrl.replace(/\/$/, "");
94
+ this.baseUrl = (config.baseUrl || "").replace(/\/$/, "");
49
95
  const urlRegex = /^https?:\/\/[^\s#$./?].\S*$/;
50
- if (!urlRegex.test(this.baseUrl)) {
96
+ if (this.baseUrl && !urlRegex.test(this.baseUrl)) {
51
97
  throw new UrlError(this.baseUrl);
52
98
  }
53
99
  this.headers = {
54
100
  "Content-Type": "application/json",
55
101
  ...config.headers
56
102
  };
103
+ this.hasCredentials = Boolean(this.baseUrl && this.headers.authorization.split(" ")[1]);
57
104
  if (this.options.responseEncoding === "base64") {
58
105
  this.headers["Upstash-Encoding"] = "base64";
59
106
  }
@@ -71,20 +118,28 @@ var HttpClient = class {
71
118
  this.headers = merge(this.headers, "Upstash-Telemetry-Sdk", telemetry.sdk);
72
119
  }
73
120
  async request(req) {
121
+ const requestHeaders = mergeHeaders(this.headers, req.headers ?? {});
122
+ const requestUrl = [this.baseUrl, ...req.path ?? []].join("/");
123
+ const isEventStream = requestHeaders.Accept === "text/event-stream";
74
124
  const requestOptions = {
75
125
  //@ts-expect-error this should throw due to bun regression
76
126
  cache: this.options.cache,
77
127
  method: "POST",
78
- headers: this.headers,
128
+ headers: requestHeaders,
79
129
  body: JSON.stringify(req.body),
80
130
  keepalive: this.options.keepAlive,
81
131
  agent: this.options.agent,
82
- signal: this.options.signal,
132
+ signal: req.signal ?? this.options.signal,
83
133
  /**
84
134
  * Fastly specific
85
135
  */
86
136
  backend: this.options.backend
87
137
  };
138
+ if (!this.hasCredentials) {
139
+ console.warn(
140
+ "[Upstash Redis] Redis client was initialized without url or token. Failed to execute command."
141
+ );
142
+ }
88
143
  if (this.readYourWrites) {
89
144
  const newHeader = this.upstashSyncToken;
90
145
  this.headers["upstash-sync-token"] = newHeader;
@@ -93,7 +148,7 @@ var HttpClient = class {
93
148
  let error = null;
94
149
  for (let i = 0; i <= this.retry.attempts; i++) {
95
150
  try {
96
- res = await fetch([this.baseUrl, ...req.path ?? []].join("/"), requestOptions);
151
+ res = await fetch(requestUrl, requestOptions);
97
152
  break;
98
153
  } catch (error_) {
99
154
  if (this.options.signal?.aborted) {
@@ -108,20 +163,54 @@ var HttpClient = class {
108
163
  break;
109
164
  }
110
165
  error = error_;
111
- await new Promise((r) => setTimeout(r, this.retry.backoff(i)));
166
+ if (i < this.retry.attempts) {
167
+ await new Promise((r) => setTimeout(r, this.retry.backoff(i)));
168
+ }
112
169
  }
113
170
  }
114
171
  if (!res) {
115
172
  throw error ?? new Error("Exhausted all retries");
116
173
  }
117
- const body = await res.json();
118
174
  if (!res.ok) {
119
- throw new UpstashError(`${body.error}, command was: ${JSON.stringify(req.body)}`);
175
+ const body2 = await res.json();
176
+ throw new UpstashError(`${body2.error}, command was: ${JSON.stringify(req.body)}`);
120
177
  }
121
178
  if (this.readYourWrites) {
122
179
  const headers = res.headers;
123
180
  this.upstashSyncToken = headers.get("upstash-sync-token") ?? "";
124
181
  }
182
+ if (isEventStream && req && req.onMessage && res.body) {
183
+ const reader = res.body.getReader();
184
+ const decoder = new TextDecoder();
185
+ (async () => {
186
+ try {
187
+ while (true) {
188
+ const { value, done } = await reader.read();
189
+ if (done) break;
190
+ const chunk = decoder.decode(value);
191
+ const lines = chunk.split("\n");
192
+ for (const line of lines) {
193
+ if (line.startsWith("data: ")) {
194
+ const data = line.slice(6);
195
+ req.onMessage?.(data);
196
+ }
197
+ }
198
+ }
199
+ } catch (error2) {
200
+ if (error2 instanceof Error && error2.name === "AbortError") {
201
+ } else {
202
+ console.error("Stream reading error:", error2);
203
+ }
204
+ } finally {
205
+ try {
206
+ await reader.cancel();
207
+ } catch {
208
+ }
209
+ }
210
+ })();
211
+ return { result: 1 };
212
+ }
213
+ const body = await res.json();
125
214
  if (this.readYourWrites) {
126
215
  const headers = res.headers;
127
216
  this.upstashSyncToken = headers.get("upstash-sync-token") ?? "";
@@ -192,31 +281,6 @@ function merge(obj, key, value) {
192
281
  return obj;
193
282
  }
194
283
 
195
- // pkg/util.ts
196
- function parseRecursive(obj) {
197
- const parsed = Array.isArray(obj) ? obj.map((o) => {
198
- try {
199
- return parseRecursive(o);
200
- } catch {
201
- return o;
202
- }
203
- }) : JSON.parse(obj);
204
- if (typeof parsed === "number" && parsed.toString() !== obj) {
205
- return obj;
206
- }
207
- return parsed;
208
- }
209
- function parseResponse(result) {
210
- try {
211
- return parseRecursive(result);
212
- } catch {
213
- return result;
214
- }
215
- }
216
- function deserializeScanResponse(result) {
217
- return [result[0], ...parseResponse(result.slice(1))];
218
- }
219
-
220
284
  // pkg/commands/command.ts
221
285
  var defaultSerializer = (c) => {
222
286
  switch (typeof c) {
@@ -234,6 +298,11 @@ var Command = class {
234
298
  command;
235
299
  serialize;
236
300
  deserialize;
301
+ headers;
302
+ path;
303
+ onMessage;
304
+ isStreaming;
305
+ signal;
237
306
  /**
238
307
  * Create a new command instance.
239
308
  *
@@ -243,6 +312,11 @@ var Command = class {
243
312
  this.serialize = defaultSerializer;
244
313
  this.deserialize = opts?.automaticDeserialization === void 0 || opts.automaticDeserialization ? opts?.deserialize ?? parseResponse : (x) => x;
245
314
  this.command = command.map((c) => this.serialize(c));
315
+ this.headers = opts?.headers;
316
+ this.path = opts?.path;
317
+ this.onMessage = opts?.streamOptions?.onMessage;
318
+ this.isStreaming = opts?.streamOptions?.isStreaming ?? false;
319
+ this.signal = opts?.streamOptions?.signal;
246
320
  if (opts?.latencyLogging) {
247
321
  const originalExec = this.exec.bind(this);
248
322
  this.exec = async (client) => {
@@ -263,7 +337,12 @@ var Command = class {
263
337
  async exec(client) {
264
338
  const { result, error } = await client.request({
265
339
  body: this.command,
266
- upstashSyncToken: client.upstashSyncToken
340
+ path: this.path,
341
+ upstashSyncToken: client.upstashSyncToken,
342
+ headers: this.headers,
343
+ onMessage: this.onMessage,
344
+ isStreaming: this.isStreaming,
345
+ signal: this.signal
267
346
  });
268
347
  if (error) {
269
348
  throw new UpstashError(error);
@@ -281,9 +360,9 @@ function deserialize(result) {
281
360
  return null;
282
361
  }
283
362
  const obj = {};
284
- while (result.length >= 2) {
285
- const key = result.shift();
286
- const value = result.shift();
363
+ for (let i = 0; i < result.length; i += 2) {
364
+ const key = result[i];
365
+ const value = result[i + 1];
287
366
  try {
288
367
  obj[key] = JSON.parse(value);
289
368
  } catch {
@@ -425,6 +504,13 @@ var EchoCommand = class extends Command {
425
504
  }
426
505
  };
427
506
 
507
+ // pkg/commands/evalRo.ts
508
+ var EvalROCommand = class extends Command {
509
+ constructor([script, keys, args], opts) {
510
+ super(["eval_ro", script, keys.length, ...keys, ...args ?? []], opts);
511
+ }
512
+ };
513
+
428
514
  // pkg/commands/eval.ts
429
515
  var EvalCommand = class extends Command {
430
516
  constructor([script, keys, args], opts) {
@@ -432,6 +518,13 @@ var EvalCommand = class extends Command {
432
518
  }
433
519
  };
434
520
 
521
+ // pkg/commands/evalshaRo.ts
522
+ var EvalshaROCommand = class extends Command {
523
+ constructor([sha, keys, args], opts) {
524
+ super(["evalsha_ro", sha, keys.length, ...keys, ...args ?? []], opts);
525
+ }
526
+ };
527
+
435
528
  // pkg/commands/evalsha.ts
436
529
  var EvalshaCommand = class extends Command {
437
530
  constructor([sha, keys, args], opts) {
@@ -439,6 +532,14 @@ var EvalshaCommand = class extends Command {
439
532
  }
440
533
  };
441
534
 
535
+ // pkg/commands/exec.ts
536
+ var ExecCommand = class extends Command {
537
+ constructor(cmd, opts) {
538
+ const normalizedCmd = cmd.map((arg) => typeof arg === "string" ? arg : String(arg));
539
+ super(normalizedCmd, opts);
540
+ }
541
+ };
542
+
442
543
  // pkg/commands/exists.ts
443
544
  var ExistsCommand = class extends Command {
444
545
  constructor(cmd, opts) {
@@ -655,6 +756,27 @@ var GetDelCommand = class extends Command {
655
756
  }
656
757
  };
657
758
 
759
+ // pkg/commands/getex.ts
760
+ var GetExCommand = class extends Command {
761
+ constructor([key, opts], cmdOpts) {
762
+ const command = ["getex", key];
763
+ if (opts) {
764
+ if ("ex" in opts && typeof opts.ex === "number") {
765
+ command.push("ex", opts.ex);
766
+ } else if ("px" in opts && typeof opts.px === "number") {
767
+ command.push("px", opts.px);
768
+ } else if ("exat" in opts && typeof opts.exat === "number") {
769
+ command.push("exat", opts.exat);
770
+ } else if ("pxat" in opts && typeof opts.pxat === "number") {
771
+ command.push("pxat", opts.pxat);
772
+ } else if ("persist" in opts && opts.persist) {
773
+ command.push("persist");
774
+ }
775
+ }
776
+ super(command, cmdOpts);
777
+ }
778
+ };
779
+
658
780
  // pkg/commands/getrange.ts
659
781
  var GetRangeCommand = class extends Command {
660
782
  constructor(cmd, opts) {
@@ -683,6 +805,122 @@ var HExistsCommand = class extends Command {
683
805
  }
684
806
  };
685
807
 
808
+ // pkg/commands/hexpire.ts
809
+ var HExpireCommand = class extends Command {
810
+ constructor(cmd, opts) {
811
+ const [key, fields, seconds, option] = cmd;
812
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
813
+ super(
814
+ [
815
+ "hexpire",
816
+ key,
817
+ seconds,
818
+ ...option ? [option] : [],
819
+ "FIELDS",
820
+ fieldArray.length,
821
+ ...fieldArray
822
+ ],
823
+ opts
824
+ );
825
+ }
826
+ };
827
+
828
+ // pkg/commands/hexpireat.ts
829
+ var HExpireAtCommand = class extends Command {
830
+ constructor(cmd, opts) {
831
+ const [key, fields, timestamp, option] = cmd;
832
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
833
+ super(
834
+ [
835
+ "hexpireat",
836
+ key,
837
+ timestamp,
838
+ ...option ? [option] : [],
839
+ "FIELDS",
840
+ fieldArray.length,
841
+ ...fieldArray
842
+ ],
843
+ opts
844
+ );
845
+ }
846
+ };
847
+
848
+ // pkg/commands/hexpiretime.ts
849
+ var HExpireTimeCommand = class extends Command {
850
+ constructor(cmd, opts) {
851
+ const [key, fields] = cmd;
852
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
853
+ super(["hexpiretime", key, "FIELDS", fieldArray.length, ...fieldArray], opts);
854
+ }
855
+ };
856
+
857
+ // pkg/commands/hpersist.ts
858
+ var HPersistCommand = class extends Command {
859
+ constructor(cmd, opts) {
860
+ const [key, fields] = cmd;
861
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
862
+ super(["hpersist", key, "FIELDS", fieldArray.length, ...fieldArray], opts);
863
+ }
864
+ };
865
+
866
+ // pkg/commands/hpexpire.ts
867
+ var HPExpireCommand = class extends Command {
868
+ constructor(cmd, opts) {
869
+ const [key, fields, milliseconds, option] = cmd;
870
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
871
+ super(
872
+ [
873
+ "hpexpire",
874
+ key,
875
+ milliseconds,
876
+ ...option ? [option] : [],
877
+ "FIELDS",
878
+ fieldArray.length,
879
+ ...fieldArray
880
+ ],
881
+ opts
882
+ );
883
+ }
884
+ };
885
+
886
+ // pkg/commands/hpexpireat.ts
887
+ var HPExpireAtCommand = class extends Command {
888
+ constructor(cmd, opts) {
889
+ const [key, fields, timestamp, option] = cmd;
890
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
891
+ super(
892
+ [
893
+ "hpexpireat",
894
+ key,
895
+ timestamp,
896
+ ...option ? [option] : [],
897
+ "FIELDS",
898
+ fieldArray.length,
899
+ ...fieldArray
900
+ ],
901
+ opts
902
+ );
903
+ }
904
+ };
905
+
906
+ // pkg/commands/hpexpiretime.ts
907
+ var HPExpireTimeCommand = class extends Command {
908
+ constructor(cmd, opts) {
909
+ const [key, fields] = cmd;
910
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
911
+ super(["hpexpiretime", key, "FIELDS", fieldArray.length, ...fieldArray], opts);
912
+ }
913
+ };
914
+
915
+ // pkg/commands/hpttl.ts
916
+ var HPTtlCommand = class extends Command {
917
+ constructor(cmd, opts) {
918
+ const [key, fields] = cmd;
919
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
920
+ super(["hpttl", key, "FIELDS", fieldArray.length, ...fieldArray], opts);
921
+ }
922
+ };
923
+
686
924
  // pkg/commands/hget.ts
687
925
  var HGetCommand = class extends Command {
688
926
  constructor(cmd, opts) {
@@ -696,9 +934,9 @@ function deserialize2(result) {
696
934
  return null;
697
935
  }
698
936
  const obj = {};
699
- while (result.length >= 2) {
700
- const key = result.shift();
701
- const value = result.shift();
937
+ for (let i = 0; i < result.length; i += 2) {
938
+ const key = result[i];
939
+ const value = result[i + 1];
702
940
  try {
703
941
  const valueIsNumberAndNotSafeInteger = !Number.isNaN(Number(value)) && !Number.isSafeInteger(Number(value));
704
942
  obj[key] = valueIsNumberAndNotSafeInteger ? value : JSON.parse(value);
@@ -814,6 +1052,15 @@ var HStrLenCommand = class extends Command {
814
1052
  }
815
1053
  };
816
1054
 
1055
+ // pkg/commands/httl.ts
1056
+ var HTtlCommand = class extends Command {
1057
+ constructor(cmd, opts) {
1058
+ const [key, fields] = cmd;
1059
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
1060
+ super(["httl", key, "FIELDS", fieldArray.length, ...fieldArray], opts);
1061
+ }
1062
+ };
1063
+
817
1064
  // pkg/commands/hvals.ts
818
1065
  var HValsCommand = class extends Command {
819
1066
  constructor(cmd, opts) {
@@ -933,6 +1180,14 @@ var JsonGetCommand = class extends Command {
933
1180
  }
934
1181
  };
935
1182
 
1183
+ // pkg/commands/json_merge.ts
1184
+ var JsonMergeCommand = class extends Command {
1185
+ constructor(cmd, opts) {
1186
+ const command = ["JSON.MERGE", ...cmd];
1187
+ super(command, opts);
1188
+ }
1189
+ };
1190
+
936
1191
  // pkg/commands/json_mget.ts
937
1192
  var JsonMGetCommand = class extends Command {
938
1193
  constructor(cmd, opts) {
@@ -1293,11 +1548,14 @@ var ScanCommand = class extends Command {
1293
1548
  if (typeof opts?.count === "number") {
1294
1549
  command.push("count", opts.count);
1295
1550
  }
1296
- if (opts?.type && opts.type.length > 0) {
1551
+ if (opts && "withType" in opts && opts.withType === true) {
1552
+ command.push("withtype");
1553
+ } else if (opts && "type" in opts && opts.type && opts.type.length > 0) {
1297
1554
  command.push("type", opts.type);
1298
1555
  }
1299
1556
  super(command, {
1300
- deserialize: deserializeScanResponse,
1557
+ // @ts-expect-error ignore types here
1558
+ deserialize: opts?.withType ? deserializeScanWithTypesResponse : deserializeScanResponse,
1301
1559
  ...cmdOpts
1302
1560
  });
1303
1561
  }
@@ -1723,15 +1981,15 @@ var XPendingCommand = class extends Command {
1723
1981
  function deserialize4(result) {
1724
1982
  const obj = {};
1725
1983
  for (const e of result) {
1726
- while (e.length >= 2) {
1727
- const streamId = e.shift();
1728
- const entries = e.shift();
1984
+ for (let i = 0; i < e.length; i += 2) {
1985
+ const streamId = e[i];
1986
+ const entries = e[i + 1];
1729
1987
  if (!(streamId in obj)) {
1730
1988
  obj[streamId] = {};
1731
1989
  }
1732
- while (entries.length >= 2) {
1733
- const field = entries.shift();
1734
- const value = entries.shift();
1990
+ for (let j = 0; j < entries.length; j += 2) {
1991
+ const field = entries[j];
1992
+ const value = entries[j + 1];
1735
1993
  try {
1736
1994
  obj[streamId][field] = JSON.parse(value);
1737
1995
  } catch {
@@ -1820,15 +2078,15 @@ var XRevRangeCommand = class extends Command {
1820
2078
  function deserialize5(result) {
1821
2079
  const obj = {};
1822
2080
  for (const e of result) {
1823
- while (e.length >= 2) {
1824
- const streamId = e.shift();
1825
- const entries = e.shift();
2081
+ for (let i = 0; i < e.length; i += 2) {
2082
+ const streamId = e[i];
2083
+ const entries = e[i + 1];
1826
2084
  if (!(streamId in obj)) {
1827
2085
  obj[streamId] = {};
1828
2086
  }
1829
- while (entries.length >= 2) {
1830
- const field = entries.shift();
1831
- const value = entries.shift();
2087
+ for (let j = 0; j < entries.length; j += 2) {
2088
+ const field = entries[j];
2089
+ const value = entries[j + 1];
1832
2090
  try {
1833
2091
  obj[streamId][field] = JSON.parse(value);
1834
2092
  } catch {
@@ -2115,9 +2373,9 @@ var Pipeline = class {
2115
2373
  this.multiExec = opts.multiExec ?? false;
2116
2374
  if (this.commandOptions?.latencyLogging) {
2117
2375
  const originalExec = this.exec.bind(this);
2118
- this.exec = async () => {
2376
+ this.exec = async (options) => {
2119
2377
  const start = performance.now();
2120
- const result = await originalExec();
2378
+ const result = await (options ? originalExec(options) : originalExec());
2121
2379
  const end = performance.now();
2122
2380
  const loggerResult = (end - start).toFixed(2);
2123
2381
  console.log(
@@ -2127,19 +2385,7 @@ var Pipeline = class {
2127
2385
  };
2128
2386
  }
2129
2387
  }
2130
- /**
2131
- * Send the pipeline request to upstash.
2132
- *
2133
- * Returns an array with the results of all pipelined commands.
2134
- *
2135
- * If all commands are statically chained from start to finish, types are inferred. You can still define a return type manually if necessary though:
2136
- * ```ts
2137
- * const p = redis.pipeline()
2138
- * p.get("key")
2139
- * const result = p.exec<[{ greeting: string }]>()
2140
- * ```
2141
- */
2142
- exec = async () => {
2388
+ exec = async (options) => {
2143
2389
  if (this.commands.length === 0) {
2144
2390
  throw new Error("Pipeline is empty");
2145
2391
  }
@@ -2148,7 +2394,12 @@ var Pipeline = class {
2148
2394
  path,
2149
2395
  body: Object.values(this.commands).map((c) => c.command)
2150
2396
  });
2151
- return res.map(({ error, result }, i) => {
2397
+ return options?.keepErrors ? res.map(({ error, result }, i) => {
2398
+ return {
2399
+ error,
2400
+ result: this.commands[i].deserialize(result)
2401
+ };
2402
+ }) : res.map(({ error, result }, i) => {
2152
2403
  if (error) {
2153
2404
  throw new UpstashError(
2154
2405
  `Command ${i + 1} [ ${this.commands[i].command[0]} ] failed: ${error}`
@@ -2234,10 +2485,18 @@ var Pipeline = class {
2234
2485
  * @see https://redis.io/commands/echo
2235
2486
  */
2236
2487
  echo = (...args) => this.chain(new EchoCommand(args, this.commandOptions));
2488
+ /**
2489
+ * @see https://redis.io/commands/eval_ro
2490
+ */
2491
+ evalRo = (...args) => this.chain(new EvalROCommand(args, this.commandOptions));
2237
2492
  /**
2238
2493
  * @see https://redis.io/commands/eval
2239
2494
  */
2240
2495
  eval = (...args) => this.chain(new EvalCommand(args, this.commandOptions));
2496
+ /**
2497
+ * @see https://redis.io/commands/evalsha_ro
2498
+ */
2499
+ evalshaRo = (...args) => this.chain(new EvalshaROCommand(args, this.commandOptions));
2241
2500
  /**
2242
2501
  * @see https://redis.io/commands/evalsha
2243
2502
  */
@@ -2298,6 +2557,10 @@ var Pipeline = class {
2298
2557
  * @see https://redis.io/commands/getdel
2299
2558
  */
2300
2559
  getdel = (...args) => this.chain(new GetDelCommand(args, this.commandOptions));
2560
+ /**
2561
+ * @see https://redis.io/commands/getex
2562
+ */
2563
+ getex = (...args) => this.chain(new GetExCommand(args, this.commandOptions));
2301
2564
  /**
2302
2565
  * @see https://redis.io/commands/getrange
2303
2566
  */
@@ -2314,6 +2577,42 @@ var Pipeline = class {
2314
2577
  * @see https://redis.io/commands/hexists
2315
2578
  */
2316
2579
  hexists = (...args) => this.chain(new HExistsCommand(args, this.commandOptions));
2580
+ /**
2581
+ * @see https://redis.io/commands/hexpire
2582
+ */
2583
+ hexpire = (...args) => this.chain(new HExpireCommand(args, this.commandOptions));
2584
+ /**
2585
+ * @see https://redis.io/commands/hexpireat
2586
+ */
2587
+ hexpireat = (...args) => this.chain(new HExpireAtCommand(args, this.commandOptions));
2588
+ /**
2589
+ * @see https://redis.io/commands/hexpiretime
2590
+ */
2591
+ hexpiretime = (...args) => this.chain(new HExpireTimeCommand(args, this.commandOptions));
2592
+ /**
2593
+ * @see https://redis.io/commands/httl
2594
+ */
2595
+ httl = (...args) => this.chain(new HTtlCommand(args, this.commandOptions));
2596
+ /**
2597
+ * @see https://redis.io/commands/hpexpire
2598
+ */
2599
+ hpexpire = (...args) => this.chain(new HPExpireCommand(args, this.commandOptions));
2600
+ /**
2601
+ * @see https://redis.io/commands/hpexpireat
2602
+ */
2603
+ hpexpireat = (...args) => this.chain(new HPExpireAtCommand(args, this.commandOptions));
2604
+ /**
2605
+ * @see https://redis.io/commands/hpexpiretime
2606
+ */
2607
+ hpexpiretime = (...args) => this.chain(new HPExpireTimeCommand(args, this.commandOptions));
2608
+ /**
2609
+ * @see https://redis.io/commands/hpttl
2610
+ */
2611
+ hpttl = (...args) => this.chain(new HPTtlCommand(args, this.commandOptions));
2612
+ /**
2613
+ * @see https://redis.io/commands/hpersist
2614
+ */
2615
+ hpersist = (...args) => this.chain(new HPersistCommand(args, this.commandOptions));
2317
2616
  /**
2318
2617
  * @see https://redis.io/commands/hget
2319
2618
  */
@@ -2831,6 +3130,10 @@ var Pipeline = class {
2831
3130
  * @see https://redis.io/commands/json.get
2832
3131
  */
2833
3132
  get: (...args) => this.chain(new JsonGetCommand(args, this.commandOptions)),
3133
+ /**
3134
+ * @see https://redis.io/commands/json.merge
3135
+ */
3136
+ merge: (...args) => this.chain(new JsonMergeCommand(args, this.commandOptions)),
2834
3137
  /**
2835
3138
  * @see https://redis.io/commands/json.mget
2836
3139
  */
@@ -2884,6 +3187,23 @@ var Pipeline = class {
2884
3187
  };
2885
3188
 
2886
3189
  // pkg/auto-pipeline.ts
3190
+ var EXCLUDE_COMMANDS = /* @__PURE__ */ new Set([
3191
+ "scan",
3192
+ "keys",
3193
+ "flushdb",
3194
+ "flushall",
3195
+ "dbsize",
3196
+ "hscan",
3197
+ "hgetall",
3198
+ "hkeys",
3199
+ "lrange",
3200
+ "sscan",
3201
+ "smembers",
3202
+ "xrange",
3203
+ "xrevrange",
3204
+ "zscan",
3205
+ "zrange"
3206
+ ]);
2887
3207
  function createAutoPipelineProxy(_redis, json) {
2888
3208
  const redis = _redis;
2889
3209
  if (!redis.autoPipelineExecutor) {
@@ -2898,7 +3218,8 @@ function createAutoPipelineProxy(_redis, json) {
2898
3218
  return createAutoPipelineProxy(redis2, true);
2899
3219
  }
2900
3220
  const commandInRedisButNotPipeline = command in redis2 && !(command in redis2.autoPipelineExecutor.pipeline);
2901
- if (commandInRedisButNotPipeline) {
3221
+ const isCommandExcluded = EXCLUDE_COMMANDS.has(command);
3222
+ if (commandInRedisButNotPipeline || isCommandExcluded) {
2902
3223
  return redis2[command];
2903
3224
  }
2904
3225
  const isFunction = json ? typeof redis2.autoPipelineExecutor.pipeline.json[command] === "function" : typeof redis2.autoPipelineExecutor.pipeline[command] === "function";
@@ -2942,7 +3263,7 @@ var AutoPipelineExecutor = class {
2942
3263
  executeWithPipeline(pipeline);
2943
3264
  const pipelineDone = this.deferExecution().then(() => {
2944
3265
  if (!this.pipelinePromises.has(pipeline)) {
2945
- const pipelinePromise = pipeline.exec();
3266
+ const pipelinePromise = pipeline.exec({ keepErrors: true });
2946
3267
  this.pipelineCounter += 1;
2947
3268
  this.pipelinePromises.set(pipeline, pipelinePromise);
2948
3269
  this.activePipeline = null;
@@ -2950,7 +3271,11 @@ var AutoPipelineExecutor = class {
2950
3271
  return this.pipelinePromises.get(pipeline);
2951
3272
  });
2952
3273
  const results = await pipelineDone;
2953
- return results[index];
3274
+ const commandResult = results[index];
3275
+ if (commandResult.error) {
3276
+ throw new UpstashError(`Command failed: ${commandResult.error}`);
3277
+ }
3278
+ return commandResult.result;
2954
3279
  }
2955
3280
  async deferExecution() {
2956
3281
  await Promise.resolve();
@@ -2958,28 +3283,221 @@ var AutoPipelineExecutor = class {
2958
3283
  }
2959
3284
  };
2960
3285
 
3286
+ // pkg/commands/psubscribe.ts
3287
+ var PSubscribeCommand = class extends Command {
3288
+ constructor(cmd, opts) {
3289
+ const sseHeaders = {
3290
+ Accept: "text/event-stream",
3291
+ "Cache-Control": "no-cache",
3292
+ Connection: "keep-alive"
3293
+ };
3294
+ super([], {
3295
+ ...opts,
3296
+ headers: sseHeaders,
3297
+ path: ["psubscribe", ...cmd],
3298
+ streamOptions: {
3299
+ isStreaming: true,
3300
+ onMessage: opts?.streamOptions?.onMessage,
3301
+ signal: opts?.streamOptions?.signal
3302
+ }
3303
+ });
3304
+ }
3305
+ };
3306
+
3307
+ // pkg/commands/subscribe.ts
3308
+ var Subscriber = class extends EventTarget {
3309
+ subscriptions;
3310
+ client;
3311
+ listeners;
3312
+ constructor(client, channels, isPattern = false) {
3313
+ super();
3314
+ this.client = client;
3315
+ this.subscriptions = /* @__PURE__ */ new Map();
3316
+ this.listeners = /* @__PURE__ */ new Map();
3317
+ for (const channel of channels) {
3318
+ if (isPattern) {
3319
+ this.subscribeToPattern(channel);
3320
+ } else {
3321
+ this.subscribeToChannel(channel);
3322
+ }
3323
+ }
3324
+ }
3325
+ subscribeToChannel(channel) {
3326
+ const controller = new AbortController();
3327
+ const command = new SubscribeCommand([channel], {
3328
+ streamOptions: {
3329
+ signal: controller.signal,
3330
+ onMessage: (data) => this.handleMessage(data, false)
3331
+ }
3332
+ });
3333
+ command.exec(this.client).catch((error) => {
3334
+ if (error.name !== "AbortError") {
3335
+ this.dispatchToListeners("error", error);
3336
+ }
3337
+ });
3338
+ this.subscriptions.set(channel, {
3339
+ command,
3340
+ controller,
3341
+ isPattern: false
3342
+ });
3343
+ }
3344
+ subscribeToPattern(pattern) {
3345
+ const controller = new AbortController();
3346
+ const command = new PSubscribeCommand([pattern], {
3347
+ streamOptions: {
3348
+ signal: controller.signal,
3349
+ onMessage: (data) => this.handleMessage(data, true)
3350
+ }
3351
+ });
3352
+ command.exec(this.client).catch((error) => {
3353
+ if (error.name !== "AbortError") {
3354
+ this.dispatchToListeners("error", error);
3355
+ }
3356
+ });
3357
+ this.subscriptions.set(pattern, {
3358
+ command,
3359
+ controller,
3360
+ isPattern: true
3361
+ });
3362
+ }
3363
+ handleMessage(data, isPattern) {
3364
+ const messageData = data.replace(/^data:\s*/, "");
3365
+ const firstCommaIndex = messageData.indexOf(",");
3366
+ const secondCommaIndex = messageData.indexOf(",", firstCommaIndex + 1);
3367
+ const thirdCommaIndex = isPattern ? messageData.indexOf(",", secondCommaIndex + 1) : -1;
3368
+ if (firstCommaIndex !== -1 && secondCommaIndex !== -1) {
3369
+ const type = messageData.slice(0, firstCommaIndex);
3370
+ if (isPattern && type === "pmessage" && thirdCommaIndex !== -1) {
3371
+ const pattern = messageData.slice(firstCommaIndex + 1, secondCommaIndex);
3372
+ const channel = messageData.slice(secondCommaIndex + 1, thirdCommaIndex);
3373
+ const messageStr = messageData.slice(thirdCommaIndex + 1);
3374
+ try {
3375
+ const message = JSON.parse(messageStr);
3376
+ this.dispatchToListeners("pmessage", { pattern, channel, message });
3377
+ this.dispatchToListeners(`pmessage:${pattern}`, { pattern, channel, message });
3378
+ } catch (error) {
3379
+ this.dispatchToListeners("error", new Error(`Failed to parse message: ${error}`));
3380
+ }
3381
+ } else {
3382
+ const channel = messageData.slice(firstCommaIndex + 1, secondCommaIndex);
3383
+ const messageStr = messageData.slice(secondCommaIndex + 1);
3384
+ try {
3385
+ if (type === "subscribe" || type === "psubscribe" || type === "unsubscribe" || type === "punsubscribe") {
3386
+ const count = Number.parseInt(messageStr);
3387
+ this.dispatchToListeners(type, count);
3388
+ } else {
3389
+ const message = JSON.parse(messageStr);
3390
+ this.dispatchToListeners(type, { channel, message });
3391
+ this.dispatchToListeners(`${type}:${channel}`, { channel, message });
3392
+ }
3393
+ } catch (error) {
3394
+ this.dispatchToListeners("error", new Error(`Failed to parse message: ${error}`));
3395
+ }
3396
+ }
3397
+ }
3398
+ }
3399
+ dispatchToListeners(type, data) {
3400
+ const listeners = this.listeners.get(type);
3401
+ if (listeners) {
3402
+ for (const listener of listeners) {
3403
+ listener(data);
3404
+ }
3405
+ }
3406
+ }
3407
+ on(type, listener) {
3408
+ if (!this.listeners.has(type)) {
3409
+ this.listeners.set(type, /* @__PURE__ */ new Set());
3410
+ }
3411
+ this.listeners.get(type)?.add(listener);
3412
+ }
3413
+ removeAllListeners() {
3414
+ this.listeners.clear();
3415
+ }
3416
+ async unsubscribe(channels) {
3417
+ if (channels) {
3418
+ for (const channel of channels) {
3419
+ const subscription = this.subscriptions.get(channel);
3420
+ if (subscription) {
3421
+ try {
3422
+ subscription.controller.abort();
3423
+ } catch {
3424
+ }
3425
+ this.subscriptions.delete(channel);
3426
+ }
3427
+ }
3428
+ } else {
3429
+ for (const subscription of this.subscriptions.values()) {
3430
+ try {
3431
+ subscription.controller.abort();
3432
+ } catch {
3433
+ }
3434
+ }
3435
+ this.subscriptions.clear();
3436
+ this.removeAllListeners();
3437
+ }
3438
+ }
3439
+ getSubscribedChannels() {
3440
+ return [...this.subscriptions.keys()];
3441
+ }
3442
+ };
3443
+ var SubscribeCommand = class extends Command {
3444
+ constructor(cmd, opts) {
3445
+ const sseHeaders = {
3446
+ Accept: "text/event-stream",
3447
+ "Cache-Control": "no-cache",
3448
+ Connection: "keep-alive"
3449
+ };
3450
+ super([], {
3451
+ ...opts,
3452
+ headers: sseHeaders,
3453
+ path: ["subscribe", ...cmd],
3454
+ streamOptions: {
3455
+ isStreaming: true,
3456
+ onMessage: opts?.streamOptions?.onMessage,
3457
+ signal: opts?.streamOptions?.signal
3458
+ }
3459
+ });
3460
+ }
3461
+ };
3462
+
2961
3463
  // pkg/script.ts
2962
- import Hex from "crypto-js/enc-hex.js";
2963
- import sha1 from "crypto-js/sha1.js";
3464
+ import { subtle } from "uncrypto";
2964
3465
  var Script = class {
2965
3466
  script;
3467
+ /**
3468
+ * @deprecated This property is initialized to an empty string and will be set in the init method
3469
+ * asynchronously. Do not use this property immidiately after the constructor.
3470
+ *
3471
+ * This property is only exposed for backwards compatibility and will be removed in the
3472
+ * future major release.
3473
+ */
2966
3474
  sha1;
2967
3475
  redis;
2968
3476
  constructor(redis, script) {
2969
3477
  this.redis = redis;
2970
- this.sha1 = this.digest(script);
2971
3478
  this.script = script;
3479
+ this.sha1 = "";
3480
+ void this.init(script);
3481
+ }
3482
+ /**
3483
+ * Initialize the script by computing its SHA-1 hash.
3484
+ */
3485
+ async init(script) {
3486
+ if (this.sha1) return;
3487
+ this.sha1 = await this.digest(script);
2972
3488
  }
2973
3489
  /**
2974
3490
  * Send an `EVAL` command to redis.
2975
3491
  */
2976
3492
  async eval(keys, args) {
3493
+ await this.init(this.script);
2977
3494
  return await this.redis.eval(this.script, keys, args);
2978
3495
  }
2979
3496
  /**
2980
3497
  * Calculates the sha1 hash of the script and then calls `EVALSHA`.
2981
3498
  */
2982
3499
  async evalsha(keys, args) {
3500
+ await this.init(this.script);
2983
3501
  return await this.redis.evalsha(this.sha1, keys, args);
2984
3502
  }
2985
3503
  /**
@@ -2989,6 +3507,7 @@ var Script = class {
2989
3507
  * Following calls will be able to use the cached script
2990
3508
  */
2991
3509
  async exec(keys, args) {
3510
+ await this.init(this.script);
2992
3511
  const res = await this.redis.evalsha(this.sha1, keys, args).catch(async (error) => {
2993
3512
  if (error instanceof Error && error.message.toLowerCase().includes("noscript")) {
2994
3513
  return await this.redis.eval(this.script, keys, args);
@@ -3000,8 +3519,75 @@ var Script = class {
3000
3519
  /**
3001
3520
  * Compute the sha1 hash of the script and return its hex representation.
3002
3521
  */
3003
- digest(s) {
3004
- return Hex.stringify(sha1(s));
3522
+ async digest(s) {
3523
+ const data = new TextEncoder().encode(s);
3524
+ const hashBuffer = await subtle.digest("SHA-1", data);
3525
+ const hashArray = [...new Uint8Array(hashBuffer)];
3526
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
3527
+ }
3528
+ };
3529
+
3530
+ // pkg/scriptRo.ts
3531
+ import { subtle as subtle2 } from "uncrypto";
3532
+ var ScriptRO = class {
3533
+ script;
3534
+ /**
3535
+ * @deprecated This property is initialized to an empty string and will be set in the init method
3536
+ * asynchronously. Do not use this property immidiately after the constructor.
3537
+ *
3538
+ * This property is only exposed for backwards compatibility and will be removed in the
3539
+ * future major release.
3540
+ */
3541
+ sha1;
3542
+ redis;
3543
+ constructor(redis, script) {
3544
+ this.redis = redis;
3545
+ this.sha1 = "";
3546
+ this.script = script;
3547
+ void this.init(script);
3548
+ }
3549
+ async init(script) {
3550
+ if (this.sha1) return;
3551
+ this.sha1 = await this.digest(script);
3552
+ }
3553
+ /**
3554
+ * Send an `EVAL_RO` command to redis.
3555
+ */
3556
+ async evalRo(keys, args) {
3557
+ await this.init(this.script);
3558
+ return await this.redis.evalRo(this.script, keys, args);
3559
+ }
3560
+ /**
3561
+ * Calculates the sha1 hash of the script and then calls `EVALSHA_RO`.
3562
+ */
3563
+ async evalshaRo(keys, args) {
3564
+ await this.init(this.script);
3565
+ return await this.redis.evalshaRo(this.sha1, keys, args);
3566
+ }
3567
+ /**
3568
+ * Optimistically try to run `EVALSHA_RO` first.
3569
+ * If the script is not loaded in redis, it will fall back and try again with `EVAL_RO`.
3570
+ *
3571
+ * Following calls will be able to use the cached script
3572
+ */
3573
+ async exec(keys, args) {
3574
+ await this.init(this.script);
3575
+ const res = await this.redis.evalshaRo(this.sha1, keys, args).catch(async (error) => {
3576
+ if (error instanceof Error && error.message.toLowerCase().includes("noscript")) {
3577
+ return await this.redis.evalRo(this.script, keys, args);
3578
+ }
3579
+ throw error;
3580
+ });
3581
+ return res;
3582
+ }
3583
+ /**
3584
+ * Compute the sha1 hash of the script and return its hex representation.
3585
+ */
3586
+ async digest(s) {
3587
+ const data = new TextEncoder().encode(s);
3588
+ const hashBuffer = await subtle2.digest("SHA-1", data);
3589
+ const hashArray = [...new Uint8Array(hashBuffer)];
3590
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
3005
3591
  }
3006
3592
  };
3007
3593
 
@@ -3031,6 +3617,12 @@ var Redis = class {
3031
3617
  }
3032
3618
  this.enableAutoPipelining = opts?.enableAutoPipelining ?? true;
3033
3619
  }
3620
+ get readYourWritesSyncToken() {
3621
+ return this.client.upstashSyncToken;
3622
+ }
3623
+ set readYourWritesSyncToken(session) {
3624
+ this.client.upstashSyncToken = session;
3625
+ }
3034
3626
  get json() {
3035
3627
  return {
3036
3628
  /**
@@ -3073,6 +3665,10 @@ var Redis = class {
3073
3665
  * @see https://redis.io/commands/json.get
3074
3666
  */
3075
3667
  get: (...args) => new JsonGetCommand(args, this.opts).exec(this.client),
3668
+ /**
3669
+ * @see https://redis.io/commands/json.merge
3670
+ */
3671
+ merge: (...args) => new JsonMergeCommand(args, this.opts).exec(this.client),
3076
3672
  /**
3077
3673
  * @see https://redis.io/commands/json.mget
3078
3674
  */
@@ -3142,8 +3738,36 @@ var Redis = class {
3142
3738
  } catch {
3143
3739
  }
3144
3740
  };
3145
- createScript(script) {
3146
- return new Script(this, script);
3741
+ /**
3742
+ * Creates a new script.
3743
+ *
3744
+ * Scripts offer the ability to optimistically try to execute a script without having to send the
3745
+ * entire script to the server. If the script is loaded on the server, it tries again by sending
3746
+ * the entire script. Afterwards, the script is cached on the server.
3747
+ *
3748
+ * @param script - The script to create
3749
+ * @param opts - Optional options to pass to the script `{ readonly?: boolean }`
3750
+ * @returns A new script
3751
+ *
3752
+ * @example
3753
+ * ```ts
3754
+ * const redis = new Redis({...})
3755
+ *
3756
+ * const script = redis.createScript<string>("return ARGV[1];")
3757
+ * const arg1 = await script.eval([], ["Hello World"])
3758
+ * expect(arg1, "Hello World")
3759
+ * ```
3760
+ * @example
3761
+ * ```ts
3762
+ * const redis = new Redis({...})
3763
+ *
3764
+ * const script = redis.createScript<string>("return ARGV[1];", { readonly: true })
3765
+ * const arg1 = await script.evalRo([], ["Hello World"])
3766
+ * expect(arg1, "Hello World")
3767
+ * ```
3768
+ */
3769
+ createScript(script, opts) {
3770
+ return opts?.readonly ? new ScriptRO(this, script) : new Script(this, script);
3147
3771
  }
3148
3772
  /**
3149
3773
  * Create a new pipeline that allows you to send requests in bulk.
@@ -3230,14 +3854,26 @@ var Redis = class {
3230
3854
  * @see https://redis.io/commands/echo
3231
3855
  */
3232
3856
  echo = (...args) => new EchoCommand(args, this.opts).exec(this.client);
3857
+ /**
3858
+ * @see https://redis.io/commands/eval_ro
3859
+ */
3860
+ evalRo = (...args) => new EvalROCommand(args, this.opts).exec(this.client);
3233
3861
  /**
3234
3862
  * @see https://redis.io/commands/eval
3235
3863
  */
3236
3864
  eval = (...args) => new EvalCommand(args, this.opts).exec(this.client);
3865
+ /**
3866
+ * @see https://redis.io/commands/evalsha_ro
3867
+ */
3868
+ evalshaRo = (...args) => new EvalshaROCommand(args, this.opts).exec(this.client);
3237
3869
  /**
3238
3870
  * @see https://redis.io/commands/evalsha
3239
3871
  */
3240
3872
  evalsha = (...args) => new EvalshaCommand(args, this.opts).exec(this.client);
3873
+ /**
3874
+ * Generic method to execute any Redis command.
3875
+ */
3876
+ exec = (args) => new ExecCommand(args, this.opts).exec(this.client);
3241
3877
  /**
3242
3878
  * @see https://redis.io/commands/exists
3243
3879
  */
@@ -3294,6 +3930,10 @@ var Redis = class {
3294
3930
  * @see https://redis.io/commands/getdel
3295
3931
  */
3296
3932
  getdel = (...args) => new GetDelCommand(args, this.opts).exec(this.client);
3933
+ /**
3934
+ * @see https://redis.io/commands/getex
3935
+ */
3936
+ getex = (...args) => new GetExCommand(args, this.opts).exec(this.client);
3297
3937
  /**
3298
3938
  * @see https://redis.io/commands/getrange
3299
3939
  */
@@ -3310,6 +3950,42 @@ var Redis = class {
3310
3950
  * @see https://redis.io/commands/hexists
3311
3951
  */
3312
3952
  hexists = (...args) => new HExistsCommand(args, this.opts).exec(this.client);
3953
+ /**
3954
+ * @see https://redis.io/commands/hexpire
3955
+ */
3956
+ hexpire = (...args) => new HExpireCommand(args, this.opts).exec(this.client);
3957
+ /**
3958
+ * @see https://redis.io/commands/hexpireat
3959
+ */
3960
+ hexpireat = (...args) => new HExpireAtCommand(args, this.opts).exec(this.client);
3961
+ /**
3962
+ * @see https://redis.io/commands/hexpiretime
3963
+ */
3964
+ hexpiretime = (...args) => new HExpireTimeCommand(args, this.opts).exec(this.client);
3965
+ /**
3966
+ * @see https://redis.io/commands/httl
3967
+ */
3968
+ httl = (...args) => new HTtlCommand(args, this.opts).exec(this.client);
3969
+ /**
3970
+ * @see https://redis.io/commands/hpexpire
3971
+ */
3972
+ hpexpire = (...args) => new HPExpireCommand(args, this.opts).exec(this.client);
3973
+ /**
3974
+ * @see https://redis.io/commands/hpexpireat
3975
+ */
3976
+ hpexpireat = (...args) => new HPExpireAtCommand(args, this.opts).exec(this.client);
3977
+ /**
3978
+ * @see https://redis.io/commands/hpexpiretime
3979
+ */
3980
+ hpexpiretime = (...args) => new HPExpireTimeCommand(args, this.opts).exec(this.client);
3981
+ /**
3982
+ * @see https://redis.io/commands/hpttl
3983
+ */
3984
+ hpttl = (...args) => new HPTtlCommand(args, this.opts).exec(this.client);
3985
+ /**
3986
+ * @see https://redis.io/commands/hpersist
3987
+ */
3988
+ hpersist = (...args) => new HPersistCommand(args, this.opts).exec(this.client);
3313
3989
  /**
3314
3990
  * @see https://redis.io/commands/hget
3315
3991
  */
@@ -3478,6 +4154,13 @@ var Redis = class {
3478
4154
  * @see https://redis.io/commands/psetex
3479
4155
  */
3480
4156
  psetex = (key, ttl, value) => new PSetEXCommand([key, ttl, value], this.opts).exec(this.client);
4157
+ /**
4158
+ * @see https://redis.io/commands/psubscribe
4159
+ */
4160
+ psubscribe = (patterns) => {
4161
+ const patternArray = Array.isArray(patterns) ? patterns : [patterns];
4162
+ return new Subscriber(this.client, patternArray, true);
4163
+ };
3481
4164
  /**
3482
4165
  * @see https://redis.io/commands/pttl
3483
4166
  */
@@ -3514,10 +4197,9 @@ var Redis = class {
3514
4197
  * @see https://redis.io/commands/sadd
3515
4198
  */
3516
4199
  sadd = (key, member, ...members) => new SAddCommand([key, member, ...members], this.opts).exec(this.client);
3517
- /**
3518
- * @see https://redis.io/commands/scan
3519
- */
3520
- scan = (...args) => new ScanCommand(args, this.opts).exec(this.client);
4200
+ scan(cursor, opts) {
4201
+ return new ScanCommand([cursor, opts], this.opts).exec(this.client);
4202
+ }
3521
4203
  /**
3522
4204
  * @see https://redis.io/commands/scard
3523
4205
  */
@@ -3606,6 +4288,13 @@ var Redis = class {
3606
4288
  * @see https://redis.io/commands/strlen
3607
4289
  */
3608
4290
  strlen = (...args) => new StrLenCommand(args, this.opts).exec(this.client);
4291
+ /**
4292
+ * @see https://redis.io/commands/subscribe
4293
+ */
4294
+ subscribe = (channels) => {
4295
+ const channelArray = Array.isArray(channels) ? channels : [channels];
4296
+ return new Subscriber(this.client, channelArray);
4297
+ };
3609
4298
  /**
3610
4299
  * @see https://redis.io/commands/sunion
3611
4300
  */
@@ -3787,7 +4476,7 @@ var Redis = class {
3787
4476
  };
3788
4477
 
3789
4478
  // version.ts
3790
- var VERSION = "v1.35.0-canary";
4479
+ var VERSION = "v1.35.0";
3791
4480
 
3792
4481
  export {
3793
4482
  error_exports,