@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/svelte.js CHANGED
@@ -239,6 +239,14 @@ var QstashDailyRatelimitError = class extends QstashError {
239
239
  this.reset = args.reset;
240
240
  }
241
241
  };
242
+ var QstashEmptyArrayError = class extends QstashError {
243
+ constructor(parameterName) {
244
+ super(
245
+ `Empty array provided for query parameter "${parameterName}". This would result in no filter being applied, which could affect all resources.`
246
+ );
247
+ this.name = "QstashEmptyArrayError";
248
+ }
249
+ };
242
250
  var QStashWorkflowError = class extends QstashError {
243
251
  constructor(message) {
244
252
  super(message);
@@ -268,6 +276,7 @@ var formatWorkflowError = (error) => {
268
276
  };
269
277
 
270
278
  // src/client/utils.ts
279
+ var DEFAULT_BULK_COUNT = 100;
271
280
  var isIgnoredHeader = (header) => {
272
281
  const lowerCaseHeader = header.toLowerCase();
273
282
  return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
@@ -354,6 +363,24 @@ function processHeaders(request) {
354
363
  if (request.label !== void 0) {
355
364
  headers.set("Upstash-Label", request.label);
356
365
  }
366
+ if (request.redact !== void 0) {
367
+ const redactParts = [];
368
+ if (request.redact.body) {
369
+ redactParts.push("body");
370
+ }
371
+ if (request.redact.header !== void 0) {
372
+ if (request.redact.header === true) {
373
+ redactParts.push("header");
374
+ } else if (Array.isArray(request.redact.header) && request.redact.header.length > 0) {
375
+ for (const headerName of request.redact.header) {
376
+ redactParts.push(`header[${headerName}]`);
377
+ }
378
+ }
379
+ }
380
+ if (redactParts.length > 0) {
381
+ headers.set("Upstash-Redact-Fields", redactParts.join(","));
382
+ }
383
+ }
357
384
  return headers;
358
385
  }
359
386
  function getRequestPath(request) {
@@ -393,38 +420,39 @@ function decodeBase64(base64) {
393
420
  }
394
421
  }
395
422
  }
396
- function buildBulkActionFilterPayload(request, options) {
397
- const hasDlqIds = "dlqIds" in request && request.dlqIds !== void 0;
398
- const hasAll = "all" in request && Boolean(request.all);
399
- const filterKeys = Object.keys(request).filter((k) => k !== "dlqIds" && k !== "all");
400
- const hasFilters = filterKeys.length > 0;
401
- if (hasDlqIds && hasAll) {
402
- throw new QstashError("dlqIds and all: true are mutually exclusive.");
403
- }
404
- if (hasDlqIds && hasFilters) {
405
- throw new QstashError(
406
- `dlqIds cannot be combined with filter fields: ${filterKeys.join(", ")}.`
407
- );
423
+ function buildBulkActionFilterPayload(request) {
424
+ const cursor = "cursor" in request ? request.cursor : void 0;
425
+ if ("all" in request) {
426
+ const count2 = "count" in request ? request.count ?? DEFAULT_BULK_COUNT : DEFAULT_BULK_COUNT;
427
+ return { count: count2, cursor };
428
+ }
429
+ if ("dlqIds" in request) {
430
+ const ids = request.dlqIds;
431
+ if (Array.isArray(ids) && ids.length === 0) {
432
+ throw new QstashError(
433
+ "Empty dlqIds array provided. If you intend to target all DLQ messages, use { all: true } explicitly."
434
+ );
435
+ }
436
+ return { dlqIds: ids, cursor };
408
437
  }
409
- if (hasAll && hasFilters) {
410
- throw new QstashError(
411
- `all: true cannot be combined with filter fields: ${filterKeys.join(", ")}.`
412
- );
438
+ if ("messageIds" in request && request.messageIds) {
439
+ if (request.messageIds.length === 0) {
440
+ throw new QstashError(
441
+ "Empty messageIds array provided. If you intend to target all messages, use { all: true } explicitly."
442
+ );
443
+ }
444
+ return { messageIds: request.messageIds, cursor };
413
445
  }
414
- if (hasAll)
415
- return {};
416
- const { urlGroup, callerIp, ...rest } = request;
417
- const payload = {
418
- ...rest,
419
- ...urlGroup === void 0 || typeof urlGroup !== "string" ? {} : { topicName: urlGroup },
420
- ...callerIp === void 0 || typeof callerIp !== "string" ? {} : options?.callerIpCasing ? { callerIP: callerIp } : { callerIp }
446
+ const count = "count" in request ? request.count ?? DEFAULT_BULK_COUNT : DEFAULT_BULK_COUNT;
447
+ return {
448
+ ...renameUrlGroup(request.filter),
449
+ count,
450
+ cursor
421
451
  };
422
- if (Object.keys(payload).length === 0) {
423
- throw new QstashError(
424
- "No filters provided. Pass { all: true } to explicitly target all messages."
425
- );
426
- }
427
- return payload;
452
+ }
453
+ function renameUrlGroup(filter) {
454
+ const { urlGroup, api, ...rest } = filter;
455
+ return { ...rest, ...urlGroup === void 0 ? {} : { topicName: urlGroup } };
428
456
  }
429
457
  function normalizeCursor(response) {
430
458
  const cursor = response.cursor;
@@ -658,23 +686,21 @@ var DLQ = class {
658
686
  }
659
687
  /**
660
688
  * List messages in the dlq
689
+ *
690
+ * Can be called with:
691
+ * - Filters: `listMessages({ filter: { url: "https://example.com" } })`
692
+ * - DLQ IDs: `listMessages({ dlqIds: ["id1", "id2"] })`
693
+ * - No filter (list all): `listMessages()`
661
694
  */
662
695
  async listMessages(options = {}) {
663
- const { urlGroup, ...restFilter } = options.filter ?? {};
664
- const filterPayload = {
665
- ...restFilter,
666
- ...urlGroup === void 0 ? {} : { topicName: urlGroup }
696
+ const query = {
697
+ count: options.count,
698
+ ..."dlqIds" in options ? { dlqIds: options.dlqIds } : { ...renameUrlGroup(options.filter ?? {}), cursor: options.cursor }
667
699
  };
668
700
  const messagesPayload = await this.http.request({
669
701
  method: "GET",
670
702
  path: ["v2", "dlq"],
671
- query: {
672
- cursor: options.cursor,
673
- count: options.count,
674
- order: options.order,
675
- trimBody: options.trimBody,
676
- ...filterPayload
677
- }
703
+ query
678
704
  });
679
705
  return {
680
706
  messages: messagesPayload.messages.map((message) => {
@@ -694,10 +720,19 @@ var DLQ = class {
694
720
  * - A single dlqId: `delete("id")`
695
721
  * - An array of dlqIds: `delete(["id1", "id2"])`
696
722
  * - An object with dlqIds: `delete({ dlqIds: ["id1", "id2"] })`
697
- * - A filter object: `delete({ url: "https://example.com", label: "label" })`
723
+ * - A filter object: `delete({ filter: { url: "https://example.com", label: "label" } })`
698
724
  * - All messages: `delete({ all: true })`
699
725
  *
700
- * Note: passing an empty array returns `{ deleted: 0 }` without making a request.
726
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
727
+ * Call in a loop until cursor is undefined:
728
+ *
729
+ * ```ts
730
+ * let cursor: string | undefined;
731
+ * do {
732
+ * const result = await dlq.delete({ all: true, count: 100, cursor });
733
+ * cursor = result.cursor;
734
+ * } while (cursor);
735
+ * ```
701
736
  */
702
737
  async delete(request) {
703
738
  if (typeof request === "string") {
@@ -711,13 +746,11 @@ var DLQ = class {
711
746
  if (Array.isArray(request) && request.length === 0)
712
747
  return { deleted: 0 };
713
748
  const filters = Array.isArray(request) ? { dlqIds: request } : request;
714
- return normalizeCursor(
715
- await this.http.request({
716
- method: "DELETE",
717
- path: ["v2", "dlq"],
718
- query: buildBulkActionFilterPayload(filters)
719
- })
720
- );
749
+ return await this.http.request({
750
+ method: "DELETE",
751
+ path: ["v2", "dlq"],
752
+ query: buildBulkActionFilterPayload(filters)
753
+ });
721
754
  }
722
755
  /**
723
756
  * Remove multiple messages from the dlq using their `dlqId`s
@@ -734,10 +767,19 @@ var DLQ = class {
734
767
  * - A single dlqId: `retry("id")`
735
768
  * - An array of dlqIds: `retry(["id1", "id2"])`
736
769
  * - An object with dlqIds: `retry({ dlqIds: ["id1", "id2"] })`
737
- * - A filter object: `retry({ url: "https://example.com", label: "label" })`
770
+ * - A filter object: `retry({ filter: { url: "https://example.com", label: "label" } })`
738
771
  * - All messages: `retry({ all: true })`
739
772
  *
740
- * Note: passing an empty array returns `{ responses: [] }` without making a request.
773
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
774
+ * Call in a loop until cursor is undefined:
775
+ *
776
+ * ```ts
777
+ * let cursor: string | undefined;
778
+ * do {
779
+ * const result = await dlq.retry({ all: true, count: 100, cursor });
780
+ * cursor = result.cursor;
781
+ * } while (cursor);
782
+ * ```
741
783
  */
742
784
  async retry(request) {
743
785
  if (typeof request === "string")
@@ -755,6 +797,107 @@ var DLQ = class {
755
797
  }
756
798
  };
757
799
 
800
+ // src/client/flow-control.ts
801
+ var FlowControlApi = class {
802
+ http;
803
+ constructor(http) {
804
+ this.http = http;
805
+ }
806
+ /**
807
+ * Get a single flow control by key.
808
+ */
809
+ async get(flowControlKey) {
810
+ return await this.http.request({
811
+ method: "GET",
812
+ path: ["v2", "flowControl", flowControlKey]
813
+ });
814
+ }
815
+ /**
816
+ * Get the global parallelism info.
817
+ */
818
+ async getGlobalParallelism() {
819
+ const response = await this.http.request({
820
+ method: "GET",
821
+ path: ["v2", "globalParallelism"]
822
+ });
823
+ return {
824
+ parallelismMax: response.parallelismMax ?? 0,
825
+ parallelismCount: response.parallelismCount ?? 0
826
+ };
827
+ }
828
+ /**
829
+ * Pause message delivery for a flow-control key.
830
+ *
831
+ * Messages already in the waitlist will remain there.
832
+ * New incoming messages will be added directly to the waitlist.
833
+ */
834
+ async pause(flowControlKey) {
835
+ await this.http.request({
836
+ method: "POST",
837
+ path: ["v2", "flowControl", flowControlKey, "pause"],
838
+ parseResponseAsJson: false
839
+ });
840
+ }
841
+ /**
842
+ * Resume message delivery for a flow-control key.
843
+ */
844
+ async resume(flowControlKey) {
845
+ await this.http.request({
846
+ method: "POST",
847
+ path: ["v2", "flowControl", flowControlKey, "resume"],
848
+ parseResponseAsJson: false
849
+ });
850
+ }
851
+ /**
852
+ * Pin a processing configuration for a flow-control key.
853
+ *
854
+ * While pinned, the system ignores configurations provided by incoming
855
+ * messages and uses the pinned configuration instead.
856
+ */
857
+ async pin(flowControlKey, options) {
858
+ await this.http.request({
859
+ method: "POST",
860
+ path: ["v2", "flowControl", flowControlKey, "pin"],
861
+ query: {
862
+ parallelism: options.parallelism,
863
+ rate: options.rate,
864
+ period: options.period
865
+ },
866
+ parseResponseAsJson: false
867
+ });
868
+ }
869
+ /**
870
+ * Remove the pinned configuration for a flow-control key.
871
+ *
872
+ * After unpinning, the system resumes updating the configuration
873
+ * based on incoming messages.
874
+ */
875
+ async unpin(flowControlKey, options) {
876
+ await this.http.request({
877
+ method: "POST",
878
+ path: ["v2", "flowControl", flowControlKey, "unpin"],
879
+ query: {
880
+ parallelism: options.parallelism,
881
+ rate: options.rate
882
+ },
883
+ parseResponseAsJson: false
884
+ });
885
+ }
886
+ /**
887
+ * Reset the rate configuration state for a flow-control key.
888
+ *
889
+ * Clears the current rate count and immediately ends the current period.
890
+ * The current timestamp becomes the start of the new rate period.
891
+ */
892
+ async resetRate(flowControlKey) {
893
+ await this.http.request({
894
+ method: "POST",
895
+ path: ["v2", "flowControl", flowControlKey, "resetRate"],
896
+ parseResponseAsJson: false
897
+ });
898
+ }
899
+ };
900
+
758
901
  // src/client/http.ts
759
902
  var HttpClient = class {
760
903
  baseUrl;
@@ -855,6 +998,9 @@ var HttpClient = class {
855
998
  if (value === void 0)
856
999
  continue;
857
1000
  if (Array.isArray(value)) {
1001
+ if (value.length === 0) {
1002
+ throw new QstashEmptyArrayError(key);
1003
+ }
858
1004
  for (const item of value) {
859
1005
  url.searchParams.append(key, item);
860
1006
  }
@@ -1096,8 +1242,19 @@ var Messages = class {
1096
1242
  * Can be called with:
1097
1243
  * - A single messageId: `cancel("id")`
1098
1244
  * - An array of messageIds: `cancel(["id1", "id2"])`
1099
- * - A filter object: `cancel({ flowControlKey: "key", label: "label" })`
1245
+ * - A filter object: `cancel({ filter: { flowControlKey: "key", label: "label" } })`
1100
1246
  * - All messages: `cancel({ all: true })`
1247
+ *
1248
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
1249
+ * Call in a loop until `cancelled` is 0:
1250
+ *
1251
+ * ```ts
1252
+ * let cancelled: number;
1253
+ * do {
1254
+ * const result = await messages.cancel({ all: true, count: 100 });
1255
+ * cancelled = result.cancelled;
1256
+ * } while (cancelled > 0);
1257
+ * ```
1101
1258
  */
1102
1259
  async cancel(request) {
1103
1260
  if (typeof request === "string") {
@@ -1112,7 +1269,7 @@ var Messages = class {
1112
1269
  return await this.http.request({
1113
1270
  method: "DELETE",
1114
1271
  path: ["v2", "messages"],
1115
- query: buildBulkActionFilterPayload(filters, { callerIpCasing: true })
1272
+ query: buildBulkActionFilterPayload(filters)
1116
1273
  });
1117
1274
  }
1118
1275
  /**
@@ -1346,6 +1503,24 @@ var Schedules = class {
1346
1503
  if (request.label !== void 0) {
1347
1504
  headers.set("Upstash-Label", request.label);
1348
1505
  }
1506
+ if (request.redact !== void 0) {
1507
+ const redactParts = [];
1508
+ if (request.redact.body) {
1509
+ redactParts.push("body");
1510
+ }
1511
+ if (request.redact.header !== void 0) {
1512
+ if (request.redact.header === true) {
1513
+ redactParts.push("header");
1514
+ } else if (Array.isArray(request.redact.header) && request.redact.header.length > 0) {
1515
+ for (const headerName of request.redact.header) {
1516
+ redactParts.push(`header[${headerName}]`);
1517
+ }
1518
+ }
1519
+ }
1520
+ if (redactParts.length > 0) {
1521
+ headers.set("Upstash-Redact-Fields", redactParts.join(","));
1522
+ }
1523
+ }
1349
1524
  return await this.http.request({
1350
1525
  method: "POST",
1351
1526
  headers: wrapWithGlobalHeaders(headers, this.http.headers, this.http.telemetryHeaders),
@@ -2861,7 +3036,7 @@ var Workflow = class {
2861
3036
  };
2862
3037
 
2863
3038
  // version.ts
2864
- var VERSION = "v2.9.1-rc.1";
3039
+ var VERSION = "2.10.0";
2865
3040
 
2866
3041
  // src/client/client.ts
2867
3042
  var Client = class {
@@ -2932,6 +3107,14 @@ var Client = class {
2932
3107
  get schedules() {
2933
3108
  return new Schedules(this.http);
2934
3109
  }
3110
+ /**
3111
+ * Access the flow control API.
3112
+ *
3113
+ * List, get, or reset flow controls.
3114
+ */
3115
+ get flowControl() {
3116
+ return new FlowControlApi(this.http);
3117
+ }
2935
3118
  /**
2936
3119
  * Access the workflow API.
2937
3120
  *
@@ -3057,38 +3240,9 @@ var Client = class {
3057
3240
  * ```
3058
3241
  */
3059
3242
  async logs(request = {}) {
3060
- const {
3061
- urlGroup,
3062
- // eslint-disable-next-line @typescript-eslint/no-deprecated
3063
- topicName,
3064
- fromDate,
3065
- toDate,
3066
- callerIp,
3067
- messageIds,
3068
- // eslint-disable-next-line @typescript-eslint/no-deprecated
3069
- count: filterCount,
3070
- ...restFilter
3071
- } = request.filter ?? {};
3072
- const filterPayload = {
3073
- ...restFilter,
3074
- topicName: urlGroup ?? topicName,
3075
- fromDate,
3076
- toDate,
3077
- callerIp
3078
- };
3079
- let cursorString;
3080
- if (typeof request.cursor === "number" && request.cursor > 0) {
3081
- cursorString = request.cursor.toString();
3082
- } else if (typeof request.cursor === "string" && request.cursor !== "") {
3083
- cursorString = request.cursor;
3084
- }
3085
3243
  const query = {
3086
- cursor: cursorString,
3087
- count: request.count ?? filterCount,
3088
- order: request.order,
3089
- trimBody: request.trimBody,
3090
- messageIds,
3091
- ...filterPayload
3244
+ count: request.count,
3245
+ ..."messageIds" in request ? { messageIds: request.messageIds } : { ...renameUrlGroup(request.filter ?? {}), cursor: request.cursor }
3092
3246
  };
3093
3247
  const responsePayload = await this.http.request({
3094
3248
  path: ["v2", "events"],
package/svelte.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import "./chunk-SN6OPGRS.mjs";
1
+ import "./chunk-FAMFGAMR.mjs";
2
2
  import {
3
3
  Receiver,
4
4
  serve
5
- } from "./chunk-RUCOF5QZ.mjs";
5
+ } from "./chunk-X3MMU3BQ.mjs";
6
6
 
7
7
  // platforms/svelte.ts
8
8
  var verifySignatureSvelte = (handler, config) => {
package/workflow.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export { an as AsyncStepFunction, ag as DisabledWorkflowContext, F as FailureFunctionPayload, aq as FinishCondition, as as LogLevel, ap as ParallelCallState, al as RawStep, ar as RequiredExceptFields, aa as RouteFunction, S as Step, ao as StepFunction, ak as StepType, aj as StepTypes, am as SyncStepFunction, ac as Workflow, ah as WorkflowClient, af as WorkflowContext, au as WorkflowLogger, at as WorkflowLoggerOptions, ai as WorkflowReceiver, ab as WorkflowServeOptions, ad as processOptions, ae as serve } from './client-Gv4WRTxB.mjs';
1
+ export { ar as AsyncStepFunction, ak as DisabledWorkflowContext, F as FailureFunctionPayload, au as FinishCondition, aw as LogLevel, at as ParallelCallState, ap as RawStep, av as RequiredExceptFields, ae as RouteFunction, S as Step, as as StepFunction, ao as StepType, an as StepTypes, aq as SyncStepFunction, ag as Workflow, al as WorkflowClient, aj as WorkflowContext, ay as WorkflowLogger, ax as WorkflowLoggerOptions, am as WorkflowReceiver, af as WorkflowServeOptions, ah as processOptions, ai as serve } from './client-CsM1dTnz.mjs';
2
2
  import 'neverthrow';
package/workflow.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { an as AsyncStepFunction, ag as DisabledWorkflowContext, F as FailureFunctionPayload, aq as FinishCondition, as as LogLevel, ap as ParallelCallState, al as RawStep, ar as RequiredExceptFields, aa as RouteFunction, S as Step, ao as StepFunction, ak as StepType, aj as StepTypes, am as SyncStepFunction, ac as Workflow, ah as WorkflowClient, af as WorkflowContext, au as WorkflowLogger, at as WorkflowLoggerOptions, ai as WorkflowReceiver, ab as WorkflowServeOptions, ad as processOptions, ae as serve } from './client-Gv4WRTxB.js';
1
+ export { ar as AsyncStepFunction, ak as DisabledWorkflowContext, F as FailureFunctionPayload, au as FinishCondition, aw as LogLevel, at as ParallelCallState, ap as RawStep, av as RequiredExceptFields, ae as RouteFunction, S as Step, as as StepFunction, ao as StepType, an as StepTypes, aq as SyncStepFunction, ag as Workflow, al as WorkflowClient, aj as WorkflowContext, ay as WorkflowLogger, ax as WorkflowLoggerOptions, am as WorkflowReceiver, af as WorkflowServeOptions, ah as processOptions, ai as serve } from './client-CsM1dTnz.js';
2
2
  import 'neverthrow';