@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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BaseProvider
3
- } from "./chunk-RUCOF5QZ.mjs";
3
+ } from "./chunk-X3MMU3BQ.mjs";
4
4
 
5
5
  // src/client/api/email.ts
6
6
  var EmailProvider = class extends BaseProvider {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Receiver,
3
3
  serve
4
- } from "./chunk-RUCOF5QZ.mjs";
4
+ } from "./chunk-X3MMU3BQ.mjs";
5
5
 
6
6
  // node_modules/defu/dist/defu.mjs
7
7
  function isPlainObject(value) {
@@ -218,6 +218,14 @@ var QstashDailyRatelimitError = class extends QstashError {
218
218
  this.reset = args.reset;
219
219
  }
220
220
  };
221
+ var QstashEmptyArrayError = class extends QstashError {
222
+ constructor(parameterName) {
223
+ super(
224
+ `Empty array provided for query parameter "${parameterName}". This would result in no filter being applied, which could affect all resources.`
225
+ );
226
+ this.name = "QstashEmptyArrayError";
227
+ }
228
+ };
221
229
  var QStashWorkflowError = class extends QstashError {
222
230
  constructor(message) {
223
231
  super(message);
@@ -247,6 +255,7 @@ var formatWorkflowError = (error) => {
247
255
  };
248
256
 
249
257
  // src/client/utils.ts
258
+ var DEFAULT_BULK_COUNT = 100;
250
259
  var isIgnoredHeader = (header) => {
251
260
  const lowerCaseHeader = header.toLowerCase();
252
261
  return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
@@ -333,6 +342,24 @@ function processHeaders(request) {
333
342
  if (request.label !== void 0) {
334
343
  headers.set("Upstash-Label", request.label);
335
344
  }
345
+ if (request.redact !== void 0) {
346
+ const redactParts = [];
347
+ if (request.redact.body) {
348
+ redactParts.push("body");
349
+ }
350
+ if (request.redact.header !== void 0) {
351
+ if (request.redact.header === true) {
352
+ redactParts.push("header");
353
+ } else if (Array.isArray(request.redact.header) && request.redact.header.length > 0) {
354
+ for (const headerName of request.redact.header) {
355
+ redactParts.push(`header[${headerName}]`);
356
+ }
357
+ }
358
+ }
359
+ if (redactParts.length > 0) {
360
+ headers.set("Upstash-Redact-Fields", redactParts.join(","));
361
+ }
362
+ }
336
363
  return headers;
337
364
  }
338
365
  function getRequestPath(request) {
@@ -372,38 +399,39 @@ function decodeBase64(base64) {
372
399
  }
373
400
  }
374
401
  }
375
- function buildBulkActionFilterPayload(request, options) {
376
- const hasDlqIds = "dlqIds" in request && request.dlqIds !== void 0;
377
- const hasAll = "all" in request && Boolean(request.all);
378
- const filterKeys = Object.keys(request).filter((k) => k !== "dlqIds" && k !== "all");
379
- const hasFilters = filterKeys.length > 0;
380
- if (hasDlqIds && hasAll) {
381
- throw new QstashError("dlqIds and all: true are mutually exclusive.");
382
- }
383
- if (hasDlqIds && hasFilters) {
384
- throw new QstashError(
385
- `dlqIds cannot be combined with filter fields: ${filterKeys.join(", ")}.`
386
- );
402
+ function buildBulkActionFilterPayload(request) {
403
+ const cursor = "cursor" in request ? request.cursor : void 0;
404
+ if ("all" in request) {
405
+ const count2 = "count" in request ? request.count ?? DEFAULT_BULK_COUNT : DEFAULT_BULK_COUNT;
406
+ return { count: count2, cursor };
407
+ }
408
+ if ("dlqIds" in request) {
409
+ const ids = request.dlqIds;
410
+ if (Array.isArray(ids) && ids.length === 0) {
411
+ throw new QstashError(
412
+ "Empty dlqIds array provided. If you intend to target all DLQ messages, use { all: true } explicitly."
413
+ );
414
+ }
415
+ return { dlqIds: ids, cursor };
387
416
  }
388
- if (hasAll && hasFilters) {
389
- throw new QstashError(
390
- `all: true cannot be combined with filter fields: ${filterKeys.join(", ")}.`
391
- );
417
+ if ("messageIds" in request && request.messageIds) {
418
+ if (request.messageIds.length === 0) {
419
+ throw new QstashError(
420
+ "Empty messageIds array provided. If you intend to target all messages, use { all: true } explicitly."
421
+ );
422
+ }
423
+ return { messageIds: request.messageIds, cursor };
392
424
  }
393
- if (hasAll)
394
- return {};
395
- const { urlGroup, callerIp, ...rest } = request;
396
- const payload = {
397
- ...rest,
398
- ...urlGroup === void 0 || typeof urlGroup !== "string" ? {} : { topicName: urlGroup },
399
- ...callerIp === void 0 || typeof callerIp !== "string" ? {} : options?.callerIpCasing ? { callerIP: callerIp } : { callerIp }
425
+ const count = "count" in request ? request.count ?? DEFAULT_BULK_COUNT : DEFAULT_BULK_COUNT;
426
+ return {
427
+ ...renameUrlGroup(request.filter),
428
+ count,
429
+ cursor
400
430
  };
401
- if (Object.keys(payload).length === 0) {
402
- throw new QstashError(
403
- "No filters provided. Pass { all: true } to explicitly target all messages."
404
- );
405
- }
406
- return payload;
431
+ }
432
+ function renameUrlGroup(filter) {
433
+ const { urlGroup, api, ...rest } = filter;
434
+ return { ...rest, ...urlGroup === void 0 ? {} : { topicName: urlGroup } };
407
435
  }
408
436
  function normalizeCursor(response) {
409
437
  const cursor = response.cursor;
@@ -637,23 +665,21 @@ var DLQ = class {
637
665
  }
638
666
  /**
639
667
  * List messages in the dlq
668
+ *
669
+ * Can be called with:
670
+ * - Filters: `listMessages({ filter: { url: "https://example.com" } })`
671
+ * - DLQ IDs: `listMessages({ dlqIds: ["id1", "id2"] })`
672
+ * - No filter (list all): `listMessages()`
640
673
  */
641
674
  async listMessages(options = {}) {
642
- const { urlGroup, ...restFilter } = options.filter ?? {};
643
- const filterPayload = {
644
- ...restFilter,
645
- ...urlGroup === void 0 ? {} : { topicName: urlGroup }
675
+ const query = {
676
+ count: options.count,
677
+ ..."dlqIds" in options ? { dlqIds: options.dlqIds } : { ...renameUrlGroup(options.filter ?? {}), cursor: options.cursor }
646
678
  };
647
679
  const messagesPayload = await this.http.request({
648
680
  method: "GET",
649
681
  path: ["v2", "dlq"],
650
- query: {
651
- cursor: options.cursor,
652
- count: options.count,
653
- order: options.order,
654
- trimBody: options.trimBody,
655
- ...filterPayload
656
- }
682
+ query
657
683
  });
658
684
  return {
659
685
  messages: messagesPayload.messages.map((message) => {
@@ -673,10 +699,19 @@ var DLQ = class {
673
699
  * - A single dlqId: `delete("id")`
674
700
  * - An array of dlqIds: `delete(["id1", "id2"])`
675
701
  * - An object with dlqIds: `delete({ dlqIds: ["id1", "id2"] })`
676
- * - A filter object: `delete({ url: "https://example.com", label: "label" })`
702
+ * - A filter object: `delete({ filter: { url: "https://example.com", label: "label" } })`
677
703
  * - All messages: `delete({ all: true })`
678
704
  *
679
- * Note: passing an empty array returns `{ deleted: 0 }` without making a request.
705
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
706
+ * Call in a loop until cursor is undefined:
707
+ *
708
+ * ```ts
709
+ * let cursor: string | undefined;
710
+ * do {
711
+ * const result = await dlq.delete({ all: true, count: 100, cursor });
712
+ * cursor = result.cursor;
713
+ * } while (cursor);
714
+ * ```
680
715
  */
681
716
  async delete(request) {
682
717
  if (typeof request === "string") {
@@ -690,13 +725,11 @@ var DLQ = class {
690
725
  if (Array.isArray(request) && request.length === 0)
691
726
  return { deleted: 0 };
692
727
  const filters = Array.isArray(request) ? { dlqIds: request } : request;
693
- return normalizeCursor(
694
- await this.http.request({
695
- method: "DELETE",
696
- path: ["v2", "dlq"],
697
- query: buildBulkActionFilterPayload(filters)
698
- })
699
- );
728
+ return await this.http.request({
729
+ method: "DELETE",
730
+ path: ["v2", "dlq"],
731
+ query: buildBulkActionFilterPayload(filters)
732
+ });
700
733
  }
701
734
  /**
702
735
  * Remove multiple messages from the dlq using their `dlqId`s
@@ -713,10 +746,19 @@ var DLQ = class {
713
746
  * - A single dlqId: `retry("id")`
714
747
  * - An array of dlqIds: `retry(["id1", "id2"])`
715
748
  * - An object with dlqIds: `retry({ dlqIds: ["id1", "id2"] })`
716
- * - A filter object: `retry({ url: "https://example.com", label: "label" })`
749
+ * - A filter object: `retry({ filter: { url: "https://example.com", label: "label" } })`
717
750
  * - All messages: `retry({ all: true })`
718
751
  *
719
- * Note: passing an empty array returns `{ responses: [] }` without making a request.
752
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
753
+ * Call in a loop until cursor is undefined:
754
+ *
755
+ * ```ts
756
+ * let cursor: string | undefined;
757
+ * do {
758
+ * const result = await dlq.retry({ all: true, count: 100, cursor });
759
+ * cursor = result.cursor;
760
+ * } while (cursor);
761
+ * ```
720
762
  */
721
763
  async retry(request) {
722
764
  if (typeof request === "string")
@@ -734,6 +776,107 @@ var DLQ = class {
734
776
  }
735
777
  };
736
778
 
779
+ // src/client/flow-control.ts
780
+ var FlowControlApi = class {
781
+ http;
782
+ constructor(http) {
783
+ this.http = http;
784
+ }
785
+ /**
786
+ * Get a single flow control by key.
787
+ */
788
+ async get(flowControlKey) {
789
+ return await this.http.request({
790
+ method: "GET",
791
+ path: ["v2", "flowControl", flowControlKey]
792
+ });
793
+ }
794
+ /**
795
+ * Get the global parallelism info.
796
+ */
797
+ async getGlobalParallelism() {
798
+ const response = await this.http.request({
799
+ method: "GET",
800
+ path: ["v2", "globalParallelism"]
801
+ });
802
+ return {
803
+ parallelismMax: response.parallelismMax ?? 0,
804
+ parallelismCount: response.parallelismCount ?? 0
805
+ };
806
+ }
807
+ /**
808
+ * Pause message delivery for a flow-control key.
809
+ *
810
+ * Messages already in the waitlist will remain there.
811
+ * New incoming messages will be added directly to the waitlist.
812
+ */
813
+ async pause(flowControlKey) {
814
+ await this.http.request({
815
+ method: "POST",
816
+ path: ["v2", "flowControl", flowControlKey, "pause"],
817
+ parseResponseAsJson: false
818
+ });
819
+ }
820
+ /**
821
+ * Resume message delivery for a flow-control key.
822
+ */
823
+ async resume(flowControlKey) {
824
+ await this.http.request({
825
+ method: "POST",
826
+ path: ["v2", "flowControl", flowControlKey, "resume"],
827
+ parseResponseAsJson: false
828
+ });
829
+ }
830
+ /**
831
+ * Pin a processing configuration for a flow-control key.
832
+ *
833
+ * While pinned, the system ignores configurations provided by incoming
834
+ * messages and uses the pinned configuration instead.
835
+ */
836
+ async pin(flowControlKey, options) {
837
+ await this.http.request({
838
+ method: "POST",
839
+ path: ["v2", "flowControl", flowControlKey, "pin"],
840
+ query: {
841
+ parallelism: options.parallelism,
842
+ rate: options.rate,
843
+ period: options.period
844
+ },
845
+ parseResponseAsJson: false
846
+ });
847
+ }
848
+ /**
849
+ * Remove the pinned configuration for a flow-control key.
850
+ *
851
+ * After unpinning, the system resumes updating the configuration
852
+ * based on incoming messages.
853
+ */
854
+ async unpin(flowControlKey, options) {
855
+ await this.http.request({
856
+ method: "POST",
857
+ path: ["v2", "flowControl", flowControlKey, "unpin"],
858
+ query: {
859
+ parallelism: options.parallelism,
860
+ rate: options.rate
861
+ },
862
+ parseResponseAsJson: false
863
+ });
864
+ }
865
+ /**
866
+ * Reset the rate configuration state for a flow-control key.
867
+ *
868
+ * Clears the current rate count and immediately ends the current period.
869
+ * The current timestamp becomes the start of the new rate period.
870
+ */
871
+ async resetRate(flowControlKey) {
872
+ await this.http.request({
873
+ method: "POST",
874
+ path: ["v2", "flowControl", flowControlKey, "resetRate"],
875
+ parseResponseAsJson: false
876
+ });
877
+ }
878
+ };
879
+
737
880
  // src/client/http.ts
738
881
  var HttpClient = class {
739
882
  baseUrl;
@@ -834,6 +977,9 @@ var HttpClient = class {
834
977
  if (value === void 0)
835
978
  continue;
836
979
  if (Array.isArray(value)) {
980
+ if (value.length === 0) {
981
+ throw new QstashEmptyArrayError(key);
982
+ }
837
983
  for (const item of value) {
838
984
  url.searchParams.append(key, item);
839
985
  }
@@ -1075,8 +1221,19 @@ var Messages = class {
1075
1221
  * Can be called with:
1076
1222
  * - A single messageId: `cancel("id")`
1077
1223
  * - An array of messageIds: `cancel(["id1", "id2"])`
1078
- * - A filter object: `cancel({ flowControlKey: "key", label: "label" })`
1224
+ * - A filter object: `cancel({ filter: { flowControlKey: "key", label: "label" } })`
1079
1225
  * - All messages: `cancel({ all: true })`
1226
+ *
1227
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
1228
+ * Call in a loop until `cancelled` is 0:
1229
+ *
1230
+ * ```ts
1231
+ * let cancelled: number;
1232
+ * do {
1233
+ * const result = await messages.cancel({ all: true, count: 100 });
1234
+ * cancelled = result.cancelled;
1235
+ * } while (cancelled > 0);
1236
+ * ```
1080
1237
  */
1081
1238
  async cancel(request) {
1082
1239
  if (typeof request === "string") {
@@ -1091,7 +1248,7 @@ var Messages = class {
1091
1248
  return await this.http.request({
1092
1249
  method: "DELETE",
1093
1250
  path: ["v2", "messages"],
1094
- query: buildBulkActionFilterPayload(filters, { callerIpCasing: true })
1251
+ query: buildBulkActionFilterPayload(filters)
1095
1252
  });
1096
1253
  }
1097
1254
  /**
@@ -1325,6 +1482,24 @@ var Schedules = class {
1325
1482
  if (request.label !== void 0) {
1326
1483
  headers.set("Upstash-Label", request.label);
1327
1484
  }
1485
+ if (request.redact !== void 0) {
1486
+ const redactParts = [];
1487
+ if (request.redact.body) {
1488
+ redactParts.push("body");
1489
+ }
1490
+ if (request.redact.header !== void 0) {
1491
+ if (request.redact.header === true) {
1492
+ redactParts.push("header");
1493
+ } else if (Array.isArray(request.redact.header) && request.redact.header.length > 0) {
1494
+ for (const headerName of request.redact.header) {
1495
+ redactParts.push(`header[${headerName}]`);
1496
+ }
1497
+ }
1498
+ }
1499
+ if (redactParts.length > 0) {
1500
+ headers.set("Upstash-Redact-Fields", redactParts.join(","));
1501
+ }
1502
+ }
1328
1503
  return await this.http.request({
1329
1504
  method: "POST",
1330
1505
  headers: wrapWithGlobalHeaders(headers, this.http.headers, this.http.telemetryHeaders),
@@ -1454,7 +1629,7 @@ var UrlGroups = class {
1454
1629
  };
1455
1630
 
1456
1631
  // version.ts
1457
- var VERSION = "v2.9.1-rc.1";
1632
+ var VERSION = "2.10.0";
1458
1633
 
1459
1634
  // src/client/client.ts
1460
1635
  var Client = class {
@@ -1525,6 +1700,14 @@ var Client = class {
1525
1700
  get schedules() {
1526
1701
  return new Schedules(this.http);
1527
1702
  }
1703
+ /**
1704
+ * Access the flow control API.
1705
+ *
1706
+ * List, get, or reset flow controls.
1707
+ */
1708
+ get flowControl() {
1709
+ return new FlowControlApi(this.http);
1710
+ }
1528
1711
  /**
1529
1712
  * Access the workflow API.
1530
1713
  *
@@ -1650,38 +1833,9 @@ var Client = class {
1650
1833
  * ```
1651
1834
  */
1652
1835
  async logs(request = {}) {
1653
- const {
1654
- urlGroup,
1655
- // eslint-disable-next-line @typescript-eslint/no-deprecated
1656
- topicName,
1657
- fromDate,
1658
- toDate,
1659
- callerIp,
1660
- messageIds,
1661
- // eslint-disable-next-line @typescript-eslint/no-deprecated
1662
- count: filterCount,
1663
- ...restFilter
1664
- } = request.filter ?? {};
1665
- const filterPayload = {
1666
- ...restFilter,
1667
- topicName: urlGroup ?? topicName,
1668
- fromDate,
1669
- toDate,
1670
- callerIp
1671
- };
1672
- let cursorString;
1673
- if (typeof request.cursor === "number" && request.cursor > 0) {
1674
- cursorString = request.cursor.toString();
1675
- } else if (typeof request.cursor === "string" && request.cursor !== "") {
1676
- cursorString = request.cursor;
1677
- }
1678
1836
  const query = {
1679
- cursor: cursorString,
1680
- count: request.count ?? filterCount,
1681
- order: request.order,
1682
- trimBody: request.trimBody,
1683
- messageIds,
1684
- ...filterPayload
1837
+ count: request.count,
1838
+ ..."messageIds" in request ? { messageIds: request.messageIds } : { ...renameUrlGroup(request.filter ?? {}), cursor: request.cursor }
1685
1839
  };
1686
1840
  const responsePayload = await this.http.request({
1687
1841
  path: ["v2", "events"],
@@ -3113,12 +3267,14 @@ export {
3113
3267
  QstashRatelimitError,
3114
3268
  QstashChatRatelimitError,
3115
3269
  QstashDailyRatelimitError,
3270
+ QstashEmptyArrayError,
3116
3271
  QStashWorkflowError,
3117
3272
  QStashWorkflowAbort,
3118
3273
  formatWorkflowError,
3119
3274
  decodeBase64,
3120
3275
  SignatureError,
3121
3276
  Receiver,
3277
+ FlowControlApi,
3122
3278
  setupAnalytics,
3123
3279
  Chat,
3124
3280
  Messages,