@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/nextjs.js CHANGED
@@ -242,6 +242,14 @@ var QstashDailyRatelimitError = class extends QstashError {
242
242
  this.reset = args.reset;
243
243
  }
244
244
  };
245
+ var QstashEmptyArrayError = class extends QstashError {
246
+ constructor(parameterName) {
247
+ super(
248
+ `Empty array provided for query parameter "${parameterName}". This would result in no filter being applied, which could affect all resources.`
249
+ );
250
+ this.name = "QstashEmptyArrayError";
251
+ }
252
+ };
245
253
  var QStashWorkflowError = class extends QstashError {
246
254
  constructor(message) {
247
255
  super(message);
@@ -271,6 +279,7 @@ var formatWorkflowError = (error) => {
271
279
  };
272
280
 
273
281
  // src/client/utils.ts
282
+ var DEFAULT_BULK_COUNT = 100;
274
283
  var isIgnoredHeader = (header) => {
275
284
  const lowerCaseHeader = header.toLowerCase();
276
285
  return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
@@ -357,6 +366,24 @@ function processHeaders(request) {
357
366
  if (request.label !== void 0) {
358
367
  headers.set("Upstash-Label", request.label);
359
368
  }
369
+ if (request.redact !== void 0) {
370
+ const redactParts = [];
371
+ if (request.redact.body) {
372
+ redactParts.push("body");
373
+ }
374
+ if (request.redact.header !== void 0) {
375
+ if (request.redact.header === true) {
376
+ redactParts.push("header");
377
+ } else if (Array.isArray(request.redact.header) && request.redact.header.length > 0) {
378
+ for (const headerName of request.redact.header) {
379
+ redactParts.push(`header[${headerName}]`);
380
+ }
381
+ }
382
+ }
383
+ if (redactParts.length > 0) {
384
+ headers.set("Upstash-Redact-Fields", redactParts.join(","));
385
+ }
386
+ }
360
387
  return headers;
361
388
  }
362
389
  function getRequestPath(request) {
@@ -396,6 +423,44 @@ function decodeBase64(base64) {
396
423
  }
397
424
  }
398
425
  }
426
+ function buildBulkActionFilterPayload(request) {
427
+ const cursor = "cursor" in request ? request.cursor : void 0;
428
+ if ("all" in request) {
429
+ const count2 = "count" in request ? request.count ?? DEFAULT_BULK_COUNT : DEFAULT_BULK_COUNT;
430
+ return { count: count2, cursor };
431
+ }
432
+ if ("dlqIds" in request) {
433
+ const ids = request.dlqIds;
434
+ if (Array.isArray(ids) && ids.length === 0) {
435
+ throw new QstashError(
436
+ "Empty dlqIds array provided. If you intend to target all DLQ messages, use { all: true } explicitly."
437
+ );
438
+ }
439
+ return { dlqIds: ids, cursor };
440
+ }
441
+ if ("messageIds" in request && request.messageIds) {
442
+ if (request.messageIds.length === 0) {
443
+ throw new QstashError(
444
+ "Empty messageIds array provided. If you intend to target all messages, use { all: true } explicitly."
445
+ );
446
+ }
447
+ return { messageIds: request.messageIds, cursor };
448
+ }
449
+ const count = "count" in request ? request.count ?? DEFAULT_BULK_COUNT : DEFAULT_BULK_COUNT;
450
+ return {
451
+ ...renameUrlGroup(request.filter),
452
+ count,
453
+ cursor
454
+ };
455
+ }
456
+ function renameUrlGroup(filter) {
457
+ const { urlGroup, api, ...rest } = filter;
458
+ return { ...rest, ...urlGroup === void 0 ? {} : { topicName: urlGroup } };
459
+ }
460
+ function normalizeCursor(response) {
461
+ const cursor = response.cursor;
462
+ return { ...response, cursor: cursor || void 0 };
463
+ }
399
464
  function getRuntime() {
400
465
  if (typeof process === "object" && typeof process.versions == "object" && process.versions.bun)
401
466
  return `bun@${process.versions.bun}`;
@@ -624,20 +689,21 @@ var DLQ = class {
624
689
  }
625
690
  /**
626
691
  * List messages in the dlq
692
+ *
693
+ * Can be called with:
694
+ * - Filters: `listMessages({ filter: { url: "https://example.com" } })`
695
+ * - DLQ IDs: `listMessages({ dlqIds: ["id1", "id2"] })`
696
+ * - No filter (list all): `listMessages()`
627
697
  */
628
- async listMessages(options) {
629
- const filterPayload = {
630
- ...options?.filter,
631
- topicName: options?.filter?.urlGroup
698
+ async listMessages(options = {}) {
699
+ const query = {
700
+ count: options.count,
701
+ ..."dlqIds" in options ? { dlqIds: options.dlqIds } : { ...renameUrlGroup(options.filter ?? {}), cursor: options.cursor }
632
702
  };
633
703
  const messagesPayload = await this.http.request({
634
704
  method: "GET",
635
705
  path: ["v2", "dlq"],
636
- query: {
637
- cursor: options?.cursor,
638
- count: options?.count,
639
- ...filterPayload
640
- }
706
+ query
641
707
  });
642
708
  return {
643
709
  messages: messagesPayload.messages.map((message) => {
@@ -651,26 +717,86 @@ var DLQ = class {
651
717
  };
652
718
  }
653
719
  /**
654
- * Remove a message from the dlq using it's `dlqId`
720
+ * Remove messages from the dlq.
721
+ *
722
+ * Can be called with:
723
+ * - A single dlqId: `delete("id")`
724
+ * - An array of dlqIds: `delete(["id1", "id2"])`
725
+ * - An object with dlqIds: `delete({ dlqIds: ["id1", "id2"] })`
726
+ * - A filter object: `delete({ filter: { url: "https://example.com", label: "label" } })`
727
+ * - All messages: `delete({ all: true })`
728
+ *
729
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
730
+ * Call in a loop until cursor is undefined:
731
+ *
732
+ * ```ts
733
+ * let cursor: string | undefined;
734
+ * do {
735
+ * const result = await dlq.delete({ all: true, count: 100, cursor });
736
+ * cursor = result.cursor;
737
+ * } while (cursor);
738
+ * ```
655
739
  */
656
- async delete(dlqMessageId) {
740
+ async delete(request) {
741
+ if (typeof request === "string") {
742
+ await this.http.request({
743
+ method: "DELETE",
744
+ path: ["v2", "dlq", request],
745
+ parseResponseAsJson: false
746
+ });
747
+ return { deleted: 1 };
748
+ }
749
+ if (Array.isArray(request) && request.length === 0)
750
+ return { deleted: 0 };
751
+ const filters = Array.isArray(request) ? { dlqIds: request } : request;
657
752
  return await this.http.request({
658
753
  method: "DELETE",
659
- path: ["v2", "dlq", dlqMessageId],
660
- parseResponseAsJson: false
661
- // there is no response
754
+ path: ["v2", "dlq"],
755
+ query: buildBulkActionFilterPayload(filters)
662
756
  });
663
757
  }
664
758
  /**
665
759
  * Remove multiple messages from the dlq using their `dlqId`s
760
+ *
761
+ * @deprecated Use `delete` instead
666
762
  */
667
763
  async deleteMany(request) {
668
- return await this.http.request({
669
- method: "DELETE",
670
- path: ["v2", "dlq"],
671
- headers: { "Content-Type": "application/json" },
672
- body: JSON.stringify({ dlqIds: request.dlqIds })
673
- });
764
+ return await this.delete(request);
765
+ }
766
+ /**
767
+ * Retry messages from the dlq.
768
+ *
769
+ * Can be called with:
770
+ * - A single dlqId: `retry("id")`
771
+ * - An array of dlqIds: `retry(["id1", "id2"])`
772
+ * - An object with dlqIds: `retry({ dlqIds: ["id1", "id2"] })`
773
+ * - A filter object: `retry({ filter: { url: "https://example.com", label: "label" } })`
774
+ * - All messages: `retry({ all: true })`
775
+ *
776
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
777
+ * Call in a loop until cursor is undefined:
778
+ *
779
+ * ```ts
780
+ * let cursor: string | undefined;
781
+ * do {
782
+ * const result = await dlq.retry({ all: true, count: 100, cursor });
783
+ * cursor = result.cursor;
784
+ * } while (cursor);
785
+ * ```
786
+ */
787
+ async retry(request) {
788
+ if (typeof request === "string")
789
+ request = [request];
790
+ if (Array.isArray(request) && request.length === 0)
791
+ return { responses: [] };
792
+ const filters = Array.isArray(request) ? { dlqIds: request } : request;
793
+ return normalizeCursor(
794
+ await this.http.request({
795
+ method: "POST",
796
+ path: ["v2", "dlq", "retry"],
797
+ query: buildBulkActionFilterPayload(filters)
798
+ })
799
+ );
674
800
  }
675
801
  };
676
802
 
@@ -872,7 +998,18 @@ var HttpClient = class {
872
998
  const url = new URL([request.baseUrl ?? this.baseUrl, ...request.path].join("/"));
873
999
  if (request.query) {
874
1000
  for (const [key, value] of Object.entries(request.query)) {
875
- if (value !== void 0) {
1001
+ if (value === void 0)
1002
+ continue;
1003
+ if (Array.isArray(value)) {
1004
+ if (value.length === 0) {
1005
+ throw new QstashEmptyArrayError(key);
1006
+ }
1007
+ for (const item of value) {
1008
+ url.searchParams.append(key, item);
1009
+ }
1010
+ } else if (value instanceof Date) {
1011
+ url.searchParams.set(key, value.getTime().toString());
1012
+ } else {
876
1013
  url.searchParams.set(key, value.toString());
877
1014
  }
878
1015
  }
@@ -1103,29 +1240,68 @@ var Messages = class {
1103
1240
  return message;
1104
1241
  }
1105
1242
  /**
1106
- * Cancel a message
1243
+ * Cancel messages.
1244
+ *
1245
+ * Can be called with:
1246
+ * - A single messageId: `cancel("id")`
1247
+ * - An array of messageIds: `cancel(["id1", "id2"])`
1248
+ * - A filter object: `cancel({ filter: { flowControlKey: "key", label: "label" } })`
1249
+ * - All messages: `cancel({ all: true })`
1250
+ *
1251
+ * Pass `count` to limit the number of messages processed per call (defaults to 100).
1252
+ * Call in a loop until `cancelled` is 0:
1253
+ *
1254
+ * ```ts
1255
+ * let cancelled: number;
1256
+ * do {
1257
+ * const result = await messages.cancel({ all: true, count: 100 });
1258
+ * cancelled = result.cancelled;
1259
+ * } while (cancelled > 0);
1260
+ * ```
1107
1261
  */
1108
- async delete(messageId) {
1262
+ async cancel(request) {
1263
+ if (typeof request === "string") {
1264
+ return await this.http.request({
1265
+ method: "DELETE",
1266
+ path: ["v2", "messages", request]
1267
+ });
1268
+ }
1269
+ if (Array.isArray(request) && request.length === 0)
1270
+ return { cancelled: 0 };
1271
+ const filters = Array.isArray(request) ? { messageIds: request } : request;
1109
1272
  return await this.http.request({
1273
+ method: "DELETE",
1274
+ path: ["v2", "messages"],
1275
+ query: buildBulkActionFilterPayload(filters)
1276
+ });
1277
+ }
1278
+ /**
1279
+ * Delete a message.
1280
+ *
1281
+ * @deprecated Use `cancel(messageId: string)` instead
1282
+ */
1283
+ async delete(messageId) {
1284
+ await this.http.request({
1110
1285
  method: "DELETE",
1111
1286
  path: ["v2", "messages", messageId],
1112
1287
  parseResponseAsJson: false
1113
1288
  });
1114
1289
  }
1290
+ /**
1291
+ * Cancel multiple messages by their messageIds.
1292
+ *
1293
+ * @deprecated Use `cancel(messageIds: string[])` instead
1294
+ */
1115
1295
  async deleteMany(messageIds) {
1116
- const result = await this.http.request({
1117
- method: "DELETE",
1118
- path: ["v2", "messages"],
1119
- headers: { "Content-Type": "application/json" },
1120
- body: JSON.stringify({ messageIds })
1121
- });
1296
+ const result = await this.cancel(messageIds);
1122
1297
  return result.cancelled;
1123
1298
  }
1299
+ /**
1300
+ * Cancel all messages
1301
+ * @deprecated Use `cancel({all: true})` to cancel all
1302
+ */
1124
1303
  async deleteAll() {
1125
- const result = await this.http.request({
1126
- method: "DELETE",
1127
- path: ["v2", "messages"]
1128
- });
1304
+ const result = await this.cancel({ all: true });
1129
1305
  return result.cancelled;
1130
1306
  }
1131
1307
  };
@@ -1330,6 +1506,24 @@ var Schedules = class {
1330
1506
  if (request.label !== void 0) {
1331
1507
  headers.set("Upstash-Label", request.label);
1332
1508
  }
1509
+ if (request.redact !== void 0) {
1510
+ const redactParts = [];
1511
+ if (request.redact.body) {
1512
+ redactParts.push("body");
1513
+ }
1514
+ if (request.redact.header !== void 0) {
1515
+ if (request.redact.header === true) {
1516
+ redactParts.push("header");
1517
+ } else if (Array.isArray(request.redact.header) && request.redact.header.length > 0) {
1518
+ for (const headerName of request.redact.header) {
1519
+ redactParts.push(`header[${headerName}]`);
1520
+ }
1521
+ }
1522
+ }
1523
+ if (redactParts.length > 0) {
1524
+ headers.set("Upstash-Redact-Fields", redactParts.join(","));
1525
+ }
1526
+ }
1333
1527
  return await this.http.request({
1334
1528
  method: "POST",
1335
1529
  headers: wrapWithGlobalHeaders(headers, this.http.headers, this.http.telemetryHeaders),
@@ -1459,7 +1653,7 @@ var UrlGroups = class {
1459
1653
  };
1460
1654
 
1461
1655
  // version.ts
1462
- var VERSION = "v2.9.1";
1656
+ var VERSION = "2.10.0";
1463
1657
 
1464
1658
  // src/client/client.ts
1465
1659
  var Client = class {
@@ -1662,39 +1856,18 @@ var Client = class {
1662
1856
  * }
1663
1857
  * ```
1664
1858
  */
1665
- async logs(request) {
1666
- const query = {};
1667
- if (typeof request?.cursor === "number" && request.cursor > 0) {
1668
- query.cursor = request.cursor.toString();
1669
- } else if (typeof request?.cursor === "string" && request.cursor !== "") {
1670
- query.cursor = request.cursor;
1671
- }
1672
- for (const [key, value] of Object.entries(request?.filter ?? {})) {
1673
- if (typeof value === "number" && value < 0) {
1674
- continue;
1675
- }
1676
- if (key === "urlGroup") {
1677
- query.topicName = value.toString();
1678
- } else if (typeof value !== "undefined") {
1679
- query[key] = value.toString();
1680
- }
1681
- }
1859
+ async logs(request = {}) {
1860
+ const query = {
1861
+ count: request.count,
1862
+ ..."messageIds" in request ? { messageIds: request.messageIds } : { ...renameUrlGroup(request.filter ?? {}), cursor: request.cursor }
1863
+ };
1682
1864
  const responsePayload = await this.http.request({
1683
1865
  path: ["v2", "events"],
1684
1866
  method: "GET",
1685
1867
  query
1686
1868
  });
1687
- const logs = responsePayload.events.map((event) => {
1688
- return {
1689
- ...event,
1690
- urlGroup: event.topicName
1691
- };
1692
- });
1693
- return {
1694
- cursor: responsePayload.cursor,
1695
- logs,
1696
- events: logs
1697
- };
1869
+ const logs = responsePayload.events.map((event) => ({ ...event, urlGroup: event.topicName }));
1870
+ return { cursor: responsePayload.cursor, logs, events: logs };
1698
1871
  }
1699
1872
  /**
1700
1873
  * @deprecated Will be removed in the next major release. Use the `logs` method instead.
package/nextjs.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Receiver,
3
3
  serve
4
- } from "./chunk-DT2X63FB.mjs";
4
+ } from "./chunk-X3MMU3BQ.mjs";
5
5
 
6
6
  // platforms/nextjs.ts
7
7
  var BAD_REQUEST = 400;
package/nuxt.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  verifySignatureH3
3
- } from "./chunk-JO26IBSH.mjs";
4
- import "./chunk-KDHOB7B5.mjs";
5
- import "./chunk-DT2X63FB.mjs";
3
+ } from "./chunk-FE7GQ4RU.mjs";
4
+ import "./chunk-FAMFGAMR.mjs";
5
+ import "./chunk-X3MMU3BQ.mjs";
6
6
 
7
7
  // platforms/nuxt.ts
8
8
  var verifySignatureNuxt = verifySignatureH3;
package/package.json CHANGED
@@ -1 +1 @@
1
- {"version":"v2.9.1","name":"@upstash/qstash","description":"Official Typescript client for QStash","author":"Andreas Thomas <dev@chronark.com>","license":"MIT","homepage":"https://github.com/upstash/qstash-js#readme","repository":{"type":"git","url":"git+https://github.com/upstash/qstash-js.git"},"bugs":{"url":"https://github.com/upstash/qstash-js/issues"},"main":"./index.js","module":"./index.mjs","types":"./index.d.ts","files":["./*"],"exports":{".":{"import":"./index.mjs","require":"./index.js"},"./dist/nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./h3":{"import":"./h3.mjs","require":"./h3.js"},"./nuxt":{"import":"./nuxt.mjs","require":"./nuxt.js"},"./svelte":{"import":"./svelte.mjs","require":"./svelte.js"},"./solidjs":{"import":"./solidjs.mjs","require":"./solidjs.js"},"./workflow":{"import":"./workflow.mjs","require":"./workflow.js"},"./hono":{"import":"./hono.mjs","require":"./hono.js"},"./cloudflare":{"import":"./cloudflare.mjs","require":"./cloudflare.js"}},"keywords":["qstash","queue","events","serverless","upstash"],"scripts":{"build":"tsup && cp README.md ./dist/ && cp package.json ./dist/ && cp LICENSE ./dist/","test":"bun test src","fmt":"prettier --write .","lint":"tsc && eslint \"{src,platforms}/**/*.{js,ts,tsx}\" --quiet --fix","check-exports":"bun run build && cd dist && attw -P"},"devDependencies":{"@commitlint/cli":"^19.2.2","@commitlint/config-conventional":"^19.2.2","@eslint/eslintrc":"^3.1.0","@eslint/js":"^9.10.0","@solidjs/start":"^1.0.6","@sveltejs/kit":"^2.5.18","@types/bun":"^1.1.1","@types/crypto-js":"^4.2.0","@typescript-eslint/eslint-plugin":"^8.4.0","@typescript-eslint/parser":"^8.4.0","bun-types":"^1.1.7","eslint":"^9.10.0","eslint-plugin-unicorn":"^51.0.1","h3":"^1.12.0","hono":"^4.5.8","husky":"^9.0.10","next":"^14.0.2","prettier":"^3.2.5","tsup":"latest","typescript":"^5.4.5","undici-types":"^6.16.0","vitest":"latest"},"dependencies":{"crypto-js":">=4.2.0","jose":"^5.2.3","neverthrow":"^7.0.1"}}
1
+ {"version":"2.10.0","name":"@upstash/qstash","description":"Official Typescript client for QStash","author":"Andreas Thomas <dev@chronark.com>","license":"MIT","homepage":"https://github.com/upstash/qstash-js#readme","repository":{"type":"git","url":"git+https://github.com/upstash/qstash-js.git"},"bugs":{"url":"https://github.com/upstash/qstash-js/issues"},"main":"./index.js","module":"./index.mjs","types":"./index.d.ts","files":["./*"],"exports":{".":{"import":"./index.mjs","require":"./index.js"},"./dist/nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./h3":{"import":"./h3.mjs","require":"./h3.js"},"./nuxt":{"import":"./nuxt.mjs","require":"./nuxt.js"},"./svelte":{"import":"./svelte.mjs","require":"./svelte.js"},"./solidjs":{"import":"./solidjs.mjs","require":"./solidjs.js"},"./workflow":{"import":"./workflow.mjs","require":"./workflow.js"},"./hono":{"import":"./hono.mjs","require":"./hono.js"},"./cloudflare":{"import":"./cloudflare.mjs","require":"./cloudflare.js"}},"keywords":["qstash","queue","events","serverless","upstash"],"scripts":{"build":"tsup && cp README.md ./dist/ && cp package.json ./dist/ && cp LICENSE ./dist/","test":"bun test src","fmt":"prettier --write .","lint":"tsc && eslint \"{src,platforms}/**/*.{js,ts,tsx}\" --quiet --fix","check-exports":"bun run build && cd dist && attw -P"},"devDependencies":{"@commitlint/cli":"^19.2.2","@commitlint/config-conventional":"^19.2.2","@eslint/eslintrc":"^3.1.0","@eslint/js":"^9.10.0","@solidjs/start":"^1.0.6","@sveltejs/kit":"^2.5.18","@types/bun":"^1.1.1","@types/crypto-js":"^4.2.0","@typescript-eslint/eslint-plugin":"^8.4.0","@typescript-eslint/parser":"^8.4.0","bun-types":"^1.1.7","eslint":"^9.10.0","eslint-plugin-unicorn":"^51.0.1","h3":"^1.12.0","hono":"^4.5.8","husky":"^9.0.10","next":"^14.0.2","prettier":"^3.2.5","tsup":"latest","typescript":"^5.4.5","undici-types":"^6.16.0","vitest":"latest"},"dependencies":{"crypto-js":">=4.2.0","jose":"^5.2.3","neverthrow":"^7.0.1"}}
package/solidjs.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { APIHandler, APIEvent } from '@solidjs/start/server';
2
- import { ae as RouteFunction, af as WorkflowServeOptions } from './client-jh_SomWB.mjs';
2
+ import { ae as RouteFunction, af as WorkflowServeOptions } from './client-CsM1dTnz.mjs';
3
3
  import 'neverthrow';
4
4
 
5
5
  type VerifySignatureConfig = {
package/solidjs.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { APIHandler, APIEvent } from '@solidjs/start/server';
2
- import { ae as RouteFunction, af as WorkflowServeOptions } from './client-jh_SomWB.js';
2
+ import { ae as RouteFunction, af as WorkflowServeOptions } from './client-CsM1dTnz.js';
3
3
  import 'neverthrow';
4
4
 
5
5
  type VerifySignatureConfig = {