@upstash/qstash 2.9.1-rc.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/h3.js CHANGED
@@ -562,6 +562,14 @@ var QstashDailyRatelimitError = class extends QstashError {
562
562
  this.reset = args.reset;
563
563
  }
564
564
  };
565
+ var QstashEmptyArrayError = class extends QstashError {
566
+ constructor(parameterName) {
567
+ super(
568
+ `Empty array provided for query parameter "${parameterName}". This would result in no filter being applied, which could affect all resources.`
569
+ );
570
+ this.name = "QstashEmptyArrayError";
571
+ }
572
+ };
565
573
  var QStashWorkflowError = class extends QstashError {
566
574
  constructor(message) {
567
575
  super(message);
@@ -591,6 +599,7 @@ var formatWorkflowError = (error) => {
591
599
  };
592
600
 
593
601
  // src/client/utils.ts
602
+ var DEFAULT_BULK_COUNT = 100;
594
603
  var isIgnoredHeader = (header) => {
595
604
  const lowerCaseHeader = header.toLowerCase();
596
605
  return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
@@ -677,6 +686,24 @@ function processHeaders(request) {
677
686
  if (request.label !== void 0) {
678
687
  headers.set("Upstash-Label", request.label);
679
688
  }
689
+ if (request.redact !== void 0) {
690
+ const redactParts = [];
691
+ if (request.redact.body) {
692
+ redactParts.push("body");
693
+ }
694
+ if (request.redact.header !== void 0) {
695
+ if (request.redact.header === true) {
696
+ redactParts.push("header");
697
+ } else if (Array.isArray(request.redact.header) && request.redact.header.length > 0) {
698
+ for (const headerName of request.redact.header) {
699
+ redactParts.push(`header[${headerName}]`);
700
+ }
701
+ }
702
+ }
703
+ if (redactParts.length > 0) {
704
+ headers.set("Upstash-Redact-Fields", redactParts.join(","));
705
+ }
706
+ }
680
707
  return headers;
681
708
  }
682
709
  function getRequestPath(request) {
@@ -716,38 +743,39 @@ function decodeBase64(base64) {
716
743
  }
717
744
  }
718
745
  }
719
- function buildBulkActionFilterPayload(request, options) {
720
- const hasDlqIds = "dlqIds" in request && request.dlqIds !== void 0;
721
- const hasAll = "all" in request && Boolean(request.all);
722
- const filterKeys = Object.keys(request).filter((k) => k !== "dlqIds" && k !== "all");
723
- const hasFilters = filterKeys.length > 0;
724
- if (hasDlqIds && hasAll) {
725
- throw new QstashError("dlqIds and all: true are mutually exclusive.");
726
- }
727
- if (hasDlqIds && hasFilters) {
728
- throw new QstashError(
729
- `dlqIds cannot be combined with filter fields: ${filterKeys.join(", ")}.`
730
- );
746
+ function buildBulkActionFilterPayload(request) {
747
+ const cursor = "cursor" in request ? request.cursor : void 0;
748
+ if ("all" in request) {
749
+ const count2 = "count" in request ? request.count ?? DEFAULT_BULK_COUNT : DEFAULT_BULK_COUNT;
750
+ return { count: count2, cursor };
751
+ }
752
+ if ("dlqIds" in request) {
753
+ const ids = request.dlqIds;
754
+ if (Array.isArray(ids) && ids.length === 0) {
755
+ throw new QstashError(
756
+ "Empty dlqIds array provided. If you intend to target all DLQ messages, use { all: true } explicitly."
757
+ );
758
+ }
759
+ return { dlqIds: ids, cursor };
731
760
  }
732
- if (hasAll && hasFilters) {
733
- throw new QstashError(
734
- `all: true cannot be combined with filter fields: ${filterKeys.join(", ")}.`
735
- );
761
+ if ("messageIds" in request && request.messageIds) {
762
+ if (request.messageIds.length === 0) {
763
+ throw new QstashError(
764
+ "Empty messageIds array provided. If you intend to target all messages, use { all: true } explicitly."
765
+ );
766
+ }
767
+ return { messageIds: request.messageIds, cursor };
736
768
  }
737
- if (hasAll)
738
- return {};
739
- const { urlGroup, callerIp, ...rest } = request;
740
- const payload = {
741
- ...rest,
742
- ...urlGroup === void 0 || typeof urlGroup !== "string" ? {} : { topicName: urlGroup },
743
- ...callerIp === void 0 || typeof callerIp !== "string" ? {} : options?.callerIpCasing ? { callerIP: callerIp } : { callerIp }
769
+ const count = "count" in request ? request.count ?? DEFAULT_BULK_COUNT : DEFAULT_BULK_COUNT;
770
+ return {
771
+ ...renameUrlGroup(request.filter),
772
+ count,
773
+ cursor
744
774
  };
745
- if (Object.keys(payload).length === 0) {
746
- throw new QstashError(
747
- "No filters provided. Pass { all: true } to explicitly target all messages."
748
- );
749
- }
750
- return payload;
775
+ }
776
+ function renameUrlGroup(filter) {
777
+ const { urlGroup, api, ...rest } = filter;
778
+ return { ...rest, ...urlGroup === void 0 ? {} : { topicName: urlGroup } };
751
779
  }
752
780
  function normalizeCursor(response) {
753
781
  const cursor = response.cursor;
@@ -981,23 +1009,21 @@ var DLQ = class {
981
1009
  }
982
1010
  /**
983
1011
  * List messages in the dlq
1012
+ *
1013
+ * Can be called with:
1014
+ * - Filters: `listMessages({ filter: { url: "https://example.com" } })`
1015
+ * - DLQ IDs: `listMessages({ dlqIds: ["id1", "id2"] })`
1016
+ * - No filter (list all): `listMessages()`
984
1017
  */
985
1018
  async listMessages(options = {}) {
986
- const { urlGroup, ...restFilter } = options.filter ?? {};
987
- const filterPayload = {
988
- ...restFilter,
989
- ...urlGroup === void 0 ? {} : { topicName: urlGroup }
1019
+ const query = {
1020
+ count: options.count,
1021
+ ..."dlqIds" in options ? { dlqIds: options.dlqIds } : { ...renameUrlGroup(options.filter ?? {}), cursor: options.cursor }
990
1022
  };
991
1023
  const messagesPayload = await this.http.request({
992
1024
  method: "GET",
993
1025
  path: ["v2", "dlq"],
994
- query: {
995
- cursor: options.cursor,
996
- count: options.count,
997
- order: options.order,
998
- trimBody: options.trimBody,
999
- ...filterPayload
1000
- }
1026
+ query
1001
1027
  });
1002
1028
  return {
1003
1029
  messages: messagesPayload.messages.map((message) => {
@@ -1017,10 +1043,19 @@ var DLQ = class {
1017
1043
  * - A single dlqId: `delete("id")`
1018
1044
  * - An array of dlqIds: `delete(["id1", "id2"])`
1019
1045
  * - An object with dlqIds: `delete({ dlqIds: ["id1", "id2"] })`
1020
- * - A filter object: `delete({ url: "https://example.com", label: "label" })`
1046
+ * - A filter object: `delete({ filter: { url: "https://example.com", label: "label" } })`
1021
1047
  * - All messages: `delete({ all: true })`
1022
1048
  *
1023
- * Note: passing an empty array returns `{ deleted: 0 }` without making a request.
1049
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
1050
+ * Call in a loop until cursor is undefined:
1051
+ *
1052
+ * ```ts
1053
+ * let cursor: string | undefined;
1054
+ * do {
1055
+ * const result = await dlq.delete({ all: true, count: 100, cursor });
1056
+ * cursor = result.cursor;
1057
+ * } while (cursor);
1058
+ * ```
1024
1059
  */
1025
1060
  async delete(request) {
1026
1061
  if (typeof request === "string") {
@@ -1034,13 +1069,11 @@ var DLQ = class {
1034
1069
  if (Array.isArray(request) && request.length === 0)
1035
1070
  return { deleted: 0 };
1036
1071
  const filters = Array.isArray(request) ? { dlqIds: request } : request;
1037
- return normalizeCursor(
1038
- await this.http.request({
1039
- method: "DELETE",
1040
- path: ["v2", "dlq"],
1041
- query: buildBulkActionFilterPayload(filters)
1042
- })
1043
- );
1072
+ return await this.http.request({
1073
+ method: "DELETE",
1074
+ path: ["v2", "dlq"],
1075
+ query: buildBulkActionFilterPayload(filters)
1076
+ });
1044
1077
  }
1045
1078
  /**
1046
1079
  * Remove multiple messages from the dlq using their `dlqId`s
@@ -1057,10 +1090,19 @@ var DLQ = class {
1057
1090
  * - A single dlqId: `retry("id")`
1058
1091
  * - An array of dlqIds: `retry(["id1", "id2"])`
1059
1092
  * - An object with dlqIds: `retry({ dlqIds: ["id1", "id2"] })`
1060
- * - A filter object: `retry({ url: "https://example.com", label: "label" })`
1093
+ * - A filter object: `retry({ filter: { url: "https://example.com", label: "label" } })`
1061
1094
  * - All messages: `retry({ all: true })`
1062
1095
  *
1063
- * Note: passing an empty array returns `{ responses: [] }` without making a request.
1096
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
1097
+ * Call in a loop until cursor is undefined:
1098
+ *
1099
+ * ```ts
1100
+ * let cursor: string | undefined;
1101
+ * do {
1102
+ * const result = await dlq.retry({ all: true, count: 100, cursor });
1103
+ * cursor = result.cursor;
1104
+ * } while (cursor);
1105
+ * ```
1064
1106
  */
1065
1107
  async retry(request) {
1066
1108
  if (typeof request === "string")
@@ -1078,6 +1120,107 @@ var DLQ = class {
1078
1120
  }
1079
1121
  };
1080
1122
 
1123
+ // src/client/flow-control.ts
1124
+ var FlowControlApi = class {
1125
+ http;
1126
+ constructor(http) {
1127
+ this.http = http;
1128
+ }
1129
+ /**
1130
+ * Get a single flow control by key.
1131
+ */
1132
+ async get(flowControlKey) {
1133
+ return await this.http.request({
1134
+ method: "GET",
1135
+ path: ["v2", "flowControl", flowControlKey]
1136
+ });
1137
+ }
1138
+ /**
1139
+ * Get the global parallelism info.
1140
+ */
1141
+ async getGlobalParallelism() {
1142
+ const response = await this.http.request({
1143
+ method: "GET",
1144
+ path: ["v2", "globalParallelism"]
1145
+ });
1146
+ return {
1147
+ parallelismMax: response.parallelismMax ?? 0,
1148
+ parallelismCount: response.parallelismCount ?? 0
1149
+ };
1150
+ }
1151
+ /**
1152
+ * Pause message delivery for a flow-control key.
1153
+ *
1154
+ * Messages already in the waitlist will remain there.
1155
+ * New incoming messages will be added directly to the waitlist.
1156
+ */
1157
+ async pause(flowControlKey) {
1158
+ await this.http.request({
1159
+ method: "POST",
1160
+ path: ["v2", "flowControl", flowControlKey, "pause"],
1161
+ parseResponseAsJson: false
1162
+ });
1163
+ }
1164
+ /**
1165
+ * Resume message delivery for a flow-control key.
1166
+ */
1167
+ async resume(flowControlKey) {
1168
+ await this.http.request({
1169
+ method: "POST",
1170
+ path: ["v2", "flowControl", flowControlKey, "resume"],
1171
+ parseResponseAsJson: false
1172
+ });
1173
+ }
1174
+ /**
1175
+ * Pin a processing configuration for a flow-control key.
1176
+ *
1177
+ * While pinned, the system ignores configurations provided by incoming
1178
+ * messages and uses the pinned configuration instead.
1179
+ */
1180
+ async pin(flowControlKey, options) {
1181
+ await this.http.request({
1182
+ method: "POST",
1183
+ path: ["v2", "flowControl", flowControlKey, "pin"],
1184
+ query: {
1185
+ parallelism: options.parallelism,
1186
+ rate: options.rate,
1187
+ period: options.period
1188
+ },
1189
+ parseResponseAsJson: false
1190
+ });
1191
+ }
1192
+ /**
1193
+ * Remove the pinned configuration for a flow-control key.
1194
+ *
1195
+ * After unpinning, the system resumes updating the configuration
1196
+ * based on incoming messages.
1197
+ */
1198
+ async unpin(flowControlKey, options) {
1199
+ await this.http.request({
1200
+ method: "POST",
1201
+ path: ["v2", "flowControl", flowControlKey, "unpin"],
1202
+ query: {
1203
+ parallelism: options.parallelism,
1204
+ rate: options.rate
1205
+ },
1206
+ parseResponseAsJson: false
1207
+ });
1208
+ }
1209
+ /**
1210
+ * Reset the rate configuration state for a flow-control key.
1211
+ *
1212
+ * Clears the current rate count and immediately ends the current period.
1213
+ * The current timestamp becomes the start of the new rate period.
1214
+ */
1215
+ async resetRate(flowControlKey) {
1216
+ await this.http.request({
1217
+ method: "POST",
1218
+ path: ["v2", "flowControl", flowControlKey, "resetRate"],
1219
+ parseResponseAsJson: false
1220
+ });
1221
+ }
1222
+ };
1223
+
1081
1224
  // src/client/http.ts
1082
1225
  var HttpClient = class {
1083
1226
  baseUrl;
@@ -1178,6 +1321,9 @@ var HttpClient = class {
1178
1321
  if (value === void 0)
1179
1322
  continue;
1180
1323
  if (Array.isArray(value)) {
1324
+ if (value.length === 0) {
1325
+ throw new QstashEmptyArrayError(key);
1326
+ }
1181
1327
  for (const item of value) {
1182
1328
  url.searchParams.append(key, item);
1183
1329
  }
@@ -1419,8 +1565,19 @@ var Messages = class {
1419
1565
  * Can be called with:
1420
1566
  * - A single messageId: `cancel("id")`
1421
1567
  * - An array of messageIds: `cancel(["id1", "id2"])`
1422
- * - A filter object: `cancel({ flowControlKey: "key", label: "label" })`
1568
+ * - A filter object: `cancel({ filter: { flowControlKey: "key", label: "label" } })`
1423
1569
  * - All messages: `cancel({ all: true })`
1570
+ *
1571
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
1572
+ * Call in a loop until `cancelled` is 0:
1573
+ *
1574
+ * ```ts
1575
+ * let cancelled: number;
1576
+ * do {
1577
+ * const result = await messages.cancel({ all: true, count: 100 });
1578
+ * cancelled = result.cancelled;
1579
+ * } while (cancelled > 0);
1580
+ * ```
1424
1581
  */
1425
1582
  async cancel(request) {
1426
1583
  if (typeof request === "string") {
@@ -1435,7 +1592,7 @@ var Messages = class {
1435
1592
  return await this.http.request({
1436
1593
  method: "DELETE",
1437
1594
  path: ["v2", "messages"],
1438
- query: buildBulkActionFilterPayload(filters, { callerIpCasing: true })
1595
+ query: buildBulkActionFilterPayload(filters)
1439
1596
  });
1440
1597
  }
1441
1598
  /**
@@ -1669,6 +1826,24 @@ var Schedules = class {
1669
1826
  if (request.label !== void 0) {
1670
1827
  headers.set("Upstash-Label", request.label);
1671
1828
  }
1829
+ if (request.redact !== void 0) {
1830
+ const redactParts = [];
1831
+ if (request.redact.body) {
1832
+ redactParts.push("body");
1833
+ }
1834
+ if (request.redact.header !== void 0) {
1835
+ if (request.redact.header === true) {
1836
+ redactParts.push("header");
1837
+ } else if (Array.isArray(request.redact.header) && request.redact.header.length > 0) {
1838
+ for (const headerName of request.redact.header) {
1839
+ redactParts.push(`header[${headerName}]`);
1840
+ }
1841
+ }
1842
+ }
1843
+ if (redactParts.length > 0) {
1844
+ headers.set("Upstash-Redact-Fields", redactParts.join(","));
1845
+ }
1846
+ }
1672
1847
  return await this.http.request({
1673
1848
  method: "POST",
1674
1849
  headers: wrapWithGlobalHeaders(headers, this.http.headers, this.http.telemetryHeaders),
@@ -3184,7 +3359,7 @@ var Workflow = class {
3184
3359
  };
3185
3360
 
3186
3361
  // version.ts
3187
- var VERSION = "v2.9.1-rc.1";
3362
+ var VERSION = "2.10.0";
3188
3363
 
3189
3364
  // src/client/client.ts
3190
3365
  var Client = class {
@@ -3255,6 +3430,14 @@ var Client = class {
3255
3430
  get schedules() {
3256
3431
  return new Schedules(this.http);
3257
3432
  }
3433
+ /**
3434
+ * Access the flow control API.
3435
+ *
3436
+ * List, get, or reset flow controls.
3437
+ */
3438
+ get flowControl() {
3439
+ return new FlowControlApi(this.http);
3440
+ }
3258
3441
  /**
3259
3442
  * Access the workflow API.
3260
3443
  *
@@ -3380,38 +3563,9 @@ var Client = class {
3380
3563
  * ```
3381
3564
  */
3382
3565
  async logs(request = {}) {
3383
- const {
3384
- urlGroup,
3385
- // eslint-disable-next-line @typescript-eslint/no-deprecated
3386
- topicName,
3387
- fromDate,
3388
- toDate,
3389
- callerIp,
3390
- messageIds,
3391
- // eslint-disable-next-line @typescript-eslint/no-deprecated
3392
- count: filterCount,
3393
- ...restFilter
3394
- } = request.filter ?? {};
3395
- const filterPayload = {
3396
- ...restFilter,
3397
- topicName: urlGroup ?? topicName,
3398
- fromDate,
3399
- toDate,
3400
- callerIp
3401
- };
3402
- let cursorString;
3403
- if (typeof request.cursor === "number" && request.cursor > 0) {
3404
- cursorString = request.cursor.toString();
3405
- } else if (typeof request.cursor === "string" && request.cursor !== "") {
3406
- cursorString = request.cursor;
3407
- }
3408
3566
  const query = {
3409
- cursor: cursorString,
3410
- count: request.count ?? filterCount,
3411
- order: request.order,
3412
- trimBody: request.trimBody,
3413
- messageIds,
3414
- ...filterPayload
3567
+ count: request.count,
3568
+ ..."messageIds" in request ? { messageIds: request.messageIds } : { ...renameUrlGroup(request.filter ?? {}), cursor: request.cursor }
3415
3569
  };
3416
3570
  const responsePayload = await this.http.request({
3417
3571
  path: ["v2", "events"],
package/h3.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  serve,
3
3
  verifySignatureH3
4
- } from "./chunk-STWPT5EV.mjs";
5
- import "./chunk-SN6OPGRS.mjs";
6
- import "./chunk-RUCOF5QZ.mjs";
4
+ } from "./chunk-FE7GQ4RU.mjs";
5
+ import "./chunk-FAMFGAMR.mjs";
6
+ import "./chunk-X3MMU3BQ.mjs";
7
7
  export {
8
8
  serve,
9
9
  verifySignatureH3
package/hono.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Context } from 'hono';
2
- import { aa as RouteFunction, ab as WorkflowServeOptions } from './client-Gv4WRTxB.mjs';
2
+ import { ae as RouteFunction, af as WorkflowServeOptions } from './client-CsM1dTnz.mjs';
3
3
  import 'neverthrow';
4
4
 
5
5
  type WorkflowBindings = {
package/hono.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Context } from 'hono';
2
- import { aa as RouteFunction, ab as WorkflowServeOptions } from './client-Gv4WRTxB.js';
2
+ import { ae as RouteFunction, af as WorkflowServeOptions } from './client-CsM1dTnz.js';
3
3
  import 'neverthrow';
4
4
 
5
5
  type WorkflowBindings = {