@upstash/qstash 2.9.1 → 2.10.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.
package/workflow.js CHANGED
@@ -244,6 +244,14 @@ var QstashDailyRatelimitError = class extends QstashError {
244
244
  this.reset = args.reset;
245
245
  }
246
246
  };
247
+ var QstashEmptyArrayError = class extends QstashError {
248
+ constructor(parameterName) {
249
+ super(
250
+ `Empty array provided for query parameter "${parameterName}". This would result in no filter being applied, which could affect all resources.`
251
+ );
252
+ this.name = "QstashEmptyArrayError";
253
+ }
254
+ };
247
255
  var QStashWorkflowError = class extends QstashError {
248
256
  constructor(message) {
249
257
  super(message);
@@ -273,6 +281,7 @@ var formatWorkflowError = (error) => {
273
281
  };
274
282
 
275
283
  // src/client/utils.ts
284
+ var DEFAULT_BULK_COUNT = 100;
276
285
  var isIgnoredHeader = (header) => {
277
286
  const lowerCaseHeader = header.toLowerCase();
278
287
  return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
@@ -359,6 +368,24 @@ function processHeaders(request) {
359
368
  if (request.label !== void 0) {
360
369
  headers.set("Upstash-Label", request.label);
361
370
  }
371
+ if (request.redact !== void 0) {
372
+ const redactParts = [];
373
+ if (request.redact.body) {
374
+ redactParts.push("body");
375
+ }
376
+ if (request.redact.header !== void 0) {
377
+ if (request.redact.header === true) {
378
+ redactParts.push("header");
379
+ } else if (Array.isArray(request.redact.header) && request.redact.header.length > 0) {
380
+ for (const headerName of request.redact.header) {
381
+ redactParts.push(`header[${headerName}]`);
382
+ }
383
+ }
384
+ }
385
+ if (redactParts.length > 0) {
386
+ headers.set("Upstash-Redact-Fields", redactParts.join(","));
387
+ }
388
+ }
362
389
  return headers;
363
390
  }
364
391
  function getRequestPath(request) {
@@ -398,6 +425,44 @@ function decodeBase64(base64) {
398
425
  }
399
426
  }
400
427
  }
428
+ function buildBulkActionFilterPayload(request) {
429
+ const cursor = "cursor" in request ? request.cursor : void 0;
430
+ if ("all" in request) {
431
+ const count2 = "count" in request ? request.count ?? DEFAULT_BULK_COUNT : DEFAULT_BULK_COUNT;
432
+ return { count: count2, cursor };
433
+ }
434
+ if ("dlqIds" in request) {
435
+ const ids = request.dlqIds;
436
+ if (Array.isArray(ids) && ids.length === 0) {
437
+ throw new QstashError(
438
+ "Empty dlqIds array provided. If you intend to target all DLQ messages, use { all: true } explicitly."
439
+ );
440
+ }
441
+ return { dlqIds: ids, cursor };
442
+ }
443
+ if ("messageIds" in request && request.messageIds) {
444
+ if (request.messageIds.length === 0) {
445
+ throw new QstashError(
446
+ "Empty messageIds array provided. If you intend to target all messages, use { all: true } explicitly."
447
+ );
448
+ }
449
+ return { messageIds: request.messageIds, cursor };
450
+ }
451
+ const count = "count" in request ? request.count ?? DEFAULT_BULK_COUNT : DEFAULT_BULK_COUNT;
452
+ return {
453
+ ...renameUrlGroup(request.filter),
454
+ count,
455
+ cursor
456
+ };
457
+ }
458
+ function renameUrlGroup(filter) {
459
+ const { urlGroup, api, ...rest } = filter;
460
+ return { ...rest, ...urlGroup === void 0 ? {} : { topicName: urlGroup } };
461
+ }
462
+ function normalizeCursor(response) {
463
+ const cursor = response.cursor;
464
+ return { ...response, cursor: cursor || void 0 };
465
+ }
401
466
  function getRuntime() {
402
467
  if (typeof process === "object" && typeof process.versions == "object" && process.versions.bun)
403
468
  return `bun@${process.versions.bun}`;
@@ -626,20 +691,21 @@ var DLQ = class {
626
691
  }
627
692
  /**
628
693
  * List messages in the dlq
694
+ *
695
+ * Can be called with:
696
+ * - Filters: `listMessages({ filter: { url: "https://example.com" } })`
697
+ * - DLQ IDs: `listMessages({ dlqIds: ["id1", "id2"] })`
698
+ * - No filter (list all): `listMessages()`
629
699
  */
630
- async listMessages(options) {
631
- const filterPayload = {
632
- ...options?.filter,
633
- topicName: options?.filter?.urlGroup
700
+ async listMessages(options = {}) {
701
+ const query = {
702
+ count: options.count,
703
+ ..."dlqIds" in options ? { dlqIds: options.dlqIds } : { ...renameUrlGroup(options.filter ?? {}), cursor: options.cursor }
634
704
  };
635
705
  const messagesPayload = await this.http.request({
636
706
  method: "GET",
637
707
  path: ["v2", "dlq"],
638
- query: {
639
- cursor: options?.cursor,
640
- count: options?.count,
641
- ...filterPayload
642
- }
708
+ query
643
709
  });
644
710
  return {
645
711
  messages: messagesPayload.messages.map((message) => {
@@ -653,26 +719,86 @@ var DLQ = class {
653
719
  };
654
720
  }
655
721
  /**
656
- * Remove a message from the dlq using it's `dlqId`
722
+ * Remove messages from the dlq.
723
+ *
724
+ * Can be called with:
725
+ * - A single dlqId: `delete("id")`
726
+ * - An array of dlqIds: `delete(["id1", "id2"])`
727
+ * - An object with dlqIds: `delete({ dlqIds: ["id1", "id2"] })`
728
+ * - A filter object: `delete({ filter: { url: "https://example.com", label: "label" } })`
729
+ * - All messages: `delete({ all: true })`
730
+ *
731
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
732
+ * Call in a loop until cursor is undefined:
733
+ *
734
+ * ```ts
735
+ * let cursor: string | undefined;
736
+ * do {
737
+ * const result = await dlq.delete({ all: true, count: 100, cursor });
738
+ * cursor = result.cursor;
739
+ * } while (cursor);
740
+ * ```
657
741
  */
658
- async delete(dlqMessageId) {
742
+ async delete(request) {
743
+ if (typeof request === "string") {
744
+ await this.http.request({
745
+ method: "DELETE",
746
+ path: ["v2", "dlq", request],
747
+ parseResponseAsJson: false
748
+ });
749
+ return { deleted: 1 };
750
+ }
751
+ if (Array.isArray(request) && request.length === 0)
752
+ return { deleted: 0 };
753
+ const filters = Array.isArray(request) ? { dlqIds: request } : request;
659
754
  return await this.http.request({
660
755
  method: "DELETE",
661
- path: ["v2", "dlq", dlqMessageId],
662
- parseResponseAsJson: false
663
- // there is no response
756
+ path: ["v2", "dlq"],
757
+ query: buildBulkActionFilterPayload(filters)
664
758
  });
665
759
  }
666
760
  /**
667
761
  * Remove multiple messages from the dlq using their `dlqId`s
762
+ *
763
+ * @deprecated Use `delete` instead
668
764
  */
669
765
  async deleteMany(request) {
670
- return await this.http.request({
671
- method: "DELETE",
672
- path: ["v2", "dlq"],
673
- headers: { "Content-Type": "application/json" },
674
- body: JSON.stringify({ dlqIds: request.dlqIds })
675
- });
766
+ return await this.delete(request);
767
+ }
768
+ /**
769
+ * Retry messages from the dlq.
770
+ *
771
+ * Can be called with:
772
+ * - A single dlqId: `retry("id")`
773
+ * - An array of dlqIds: `retry(["id1", "id2"])`
774
+ * - An object with dlqIds: `retry({ dlqIds: ["id1", "id2"] })`
775
+ * - A filter object: `retry({ filter: { url: "https://example.com", label: "label" } })`
776
+ * - All messages: `retry({ all: true })`
777
+ *
778
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
779
+ * Call in a loop until cursor is undefined:
780
+ *
781
+ * ```ts
782
+ * let cursor: string | undefined;
783
+ * do {
784
+ * const result = await dlq.retry({ all: true, count: 100, cursor });
785
+ * cursor = result.cursor;
786
+ * } while (cursor);
787
+ * ```
788
+ */
789
+ async retry(request) {
790
+ if (typeof request === "string")
791
+ request = [request];
792
+ if (Array.isArray(request) && request.length === 0)
793
+ return { responses: [] };
794
+ const filters = Array.isArray(request) ? { dlqIds: request } : request;
795
+ return normalizeCursor(
796
+ await this.http.request({
797
+ method: "POST",
798
+ path: ["v2", "dlq", "retry"],
799
+ query: buildBulkActionFilterPayload(filters)
800
+ })
801
+ );
676
802
  }
677
803
  };
678
804
 
@@ -874,7 +1000,18 @@ var HttpClient = class {
874
1000
  const url = new URL([request.baseUrl ?? this.baseUrl, ...request.path].join("/"));
875
1001
  if (request.query) {
876
1002
  for (const [key, value] of Object.entries(request.query)) {
877
- if (value !== void 0) {
1003
+ if (value === void 0)
1004
+ continue;
1005
+ if (Array.isArray(value)) {
1006
+ if (value.length === 0) {
1007
+ throw new QstashEmptyArrayError(key);
1008
+ }
1009
+ for (const item of value) {
1010
+ url.searchParams.append(key, item);
1011
+ }
1012
+ } else if (value instanceof Date) {
1013
+ url.searchParams.set(key, value.getTime().toString());
1014
+ } else {
878
1015
  url.searchParams.set(key, value.toString());
879
1016
  }
880
1017
  }
@@ -1105,29 +1242,68 @@ var Messages = class {
1105
1242
  return message;
1106
1243
  }
1107
1244
  /**
1108
- * Cancel a message
1245
+ * Cancel messages.
1246
+ *
1247
+ * Can be called with:
1248
+ * - A single messageId: `cancel("id")`
1249
+ * - An array of messageIds: `cancel(["id1", "id2"])`
1250
+ * - A filter object: `cancel({ filter: { flowControlKey: "key", label: "label" } })`
1251
+ * - All messages: `cancel({ all: true })`
1252
+ *
1253
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
1254
+ * Call in a loop until `cancelled` is 0:
1255
+ *
1256
+ * ```ts
1257
+ * let cancelled: number;
1258
+ * do {
1259
+ * const result = await messages.cancel({ all: true, count: 100 });
1260
+ * cancelled = result.cancelled;
1261
+ * } while (cancelled > 0);
1262
+ * ```
1109
1263
  */
1110
- async delete(messageId) {
1264
+ async cancel(request) {
1265
+ if (typeof request === "string") {
1266
+ return await this.http.request({
1267
+ method: "DELETE",
1268
+ path: ["v2", "messages", request]
1269
+ });
1270
+ }
1271
+ if (Array.isArray(request) && request.length === 0)
1272
+ return { cancelled: 0 };
1273
+ const filters = Array.isArray(request) ? { messageIds: request } : request;
1111
1274
  return await this.http.request({
1275
+ method: "DELETE",
1276
+ path: ["v2", "messages"],
1277
+ query: buildBulkActionFilterPayload(filters)
1278
+ });
1279
+ }
1280
+ /**
1281
+ * Delete a message.
1282
+ *
1283
+ * @deprecated Use `cancel(messageId: string)` instead
1284
+ */
1285
+ async delete(messageId) {
1286
+ await this.http.request({
1112
1287
  method: "DELETE",
1113
1288
  path: ["v2", "messages", messageId],
1114
1289
  parseResponseAsJson: false
1115
1290
  });
1116
1291
  }
1292
+ /**
1293
+ * Cancel multiple messages by their messageIds.
1294
+ *
1295
+ * @deprecated Use `cancel(messageIds: string[])` instead
1296
+ */
1117
1297
  async deleteMany(messageIds) {
1118
- const result = await this.http.request({
1119
- method: "DELETE",
1120
- path: ["v2", "messages"],
1121
- headers: { "Content-Type": "application/json" },
1122
- body: JSON.stringify({ messageIds })
1123
- });
1298
+ const result = await this.cancel(messageIds);
1124
1299
  return result.cancelled;
1125
1300
  }
1301
+ /**
1302
+ * Cancel all messages
1303
+ * @deprecated Use `cancel({all: true})` to cancel all
1304
+ */
1126
1305
  async deleteAll() {
1127
- const result = await this.http.request({
1128
- method: "DELETE",
1129
- path: ["v2", "messages"]
1130
- });
1306
+ const result = await this.cancel({ all: true });
1131
1307
  return result.cancelled;
1132
1308
  }
1133
1309
  };
@@ -1332,6 +1508,24 @@ var Schedules = class {
1332
1508
  if (request.label !== void 0) {
1333
1509
  headers.set("Upstash-Label", request.label);
1334
1510
  }
1511
+ if (request.redact !== void 0) {
1512
+ const redactParts = [];
1513
+ if (request.redact.body) {
1514
+ redactParts.push("body");
1515
+ }
1516
+ if (request.redact.header !== void 0) {
1517
+ if (request.redact.header === true) {
1518
+ redactParts.push("header");
1519
+ } else if (Array.isArray(request.redact.header) && request.redact.header.length > 0) {
1520
+ for (const headerName of request.redact.header) {
1521
+ redactParts.push(`header[${headerName}]`);
1522
+ }
1523
+ }
1524
+ }
1525
+ if (redactParts.length > 0) {
1526
+ headers.set("Upstash-Redact-Fields", redactParts.join(","));
1527
+ }
1528
+ }
1335
1529
  return await this.http.request({
1336
1530
  method: "POST",
1337
1531
  headers: wrapWithGlobalHeaders(headers, this.http.headers, this.http.telemetryHeaders),
@@ -1461,7 +1655,7 @@ var UrlGroups = class {
1461
1655
  };
1462
1656
 
1463
1657
  // version.ts
1464
- var VERSION = "v2.9.1";
1658
+ var VERSION = "2.10.0";
1465
1659
 
1466
1660
  // src/client/client.ts
1467
1661
  var Client = class {
@@ -1664,39 +1858,18 @@ var Client = class {
1664
1858
  * }
1665
1859
  * ```
1666
1860
  */
1667
- async logs(request) {
1668
- const query = {};
1669
- if (typeof request?.cursor === "number" && request.cursor > 0) {
1670
- query.cursor = request.cursor.toString();
1671
- } else if (typeof request?.cursor === "string" && request.cursor !== "") {
1672
- query.cursor = request.cursor;
1673
- }
1674
- for (const [key, value] of Object.entries(request?.filter ?? {})) {
1675
- if (typeof value === "number" && value < 0) {
1676
- continue;
1677
- }
1678
- if (key === "urlGroup") {
1679
- query.topicName = value.toString();
1680
- } else if (typeof value !== "undefined") {
1681
- query[key] = value.toString();
1682
- }
1683
- }
1861
+ async logs(request = {}) {
1862
+ const query = {
1863
+ count: request.count,
1864
+ ..."messageIds" in request ? { messageIds: request.messageIds } : { ...renameUrlGroup(request.filter ?? {}), cursor: request.cursor }
1865
+ };
1684
1866
  const responsePayload = await this.http.request({
1685
1867
  path: ["v2", "events"],
1686
1868
  method: "GET",
1687
1869
  query
1688
1870
  });
1689
- const logs = responsePayload.events.map((event) => {
1690
- return {
1691
- ...event,
1692
- urlGroup: event.topicName
1693
- };
1694
- });
1695
- return {
1696
- cursor: responsePayload.cursor,
1697
- logs,
1698
- events: logs
1699
- };
1871
+ const logs = responsePayload.events.map((event) => ({ ...event, urlGroup: event.topicName }));
1872
+ return { cursor: responsePayload.cursor, logs, events: logs };
1700
1873
  }
1701
1874
  /**
1702
1875
  * @deprecated Will be removed in the next major release. Use the `logs` method instead.
package/workflow.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  WorkflowLogger,
7
7
  processOptions,
8
8
  serve
9
- } from "./chunk-DT2X63FB.mjs";
9
+ } from "./chunk-X3MMU3BQ.mjs";
10
10
  export {
11
11
  DisabledWorkflowContext,
12
12
  StepTypes,