@upstash/qstash 2.7.13 → 2.7.15

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/index.js CHANGED
@@ -43,10 +43,12 @@ __export(src_exports, {
43
43
  Schedules: () => Schedules,
44
44
  SignatureError: () => SignatureError,
45
45
  UrlGroups: () => UrlGroups,
46
+ anthropic: () => anthropic,
46
47
  custom: () => custom,
47
48
  decodeBase64: () => decodeBase64,
48
49
  formatWorkflowError: () => formatWorkflowError,
49
50
  openai: () => openai,
51
+ resend: () => resend,
50
52
  setupAnalytics: () => setupAnalytics,
51
53
  upstash: () => upstash
52
54
  });
@@ -78,11 +80,14 @@ var Receiver = class {
78
80
  * If that fails, the signature is invalid and a `SignatureError` is thrown.
79
81
  */
80
82
  async verify(request) {
81
- const isValid = await this.verifyWithKey(this.currentSigningKey, request);
82
- if (isValid) {
83
- return true;
83
+ let payload;
84
+ try {
85
+ payload = await this.verifyWithKey(this.currentSigningKey, request);
86
+ } catch {
87
+ payload = await this.verifyWithKey(this.nextSigningKey, request);
84
88
  }
85
- return this.verifyWithKey(this.nextSigningKey, request);
89
+ this.verifyBodyAndUrl(payload, request);
90
+ return true;
86
91
  }
87
92
  /**
88
93
  * Verify signature with a specific signing key
@@ -94,7 +99,10 @@ var Receiver = class {
94
99
  }).catch((error) => {
95
100
  throw new SignatureError(error.message);
96
101
  });
97
- const p = jwt.payload;
102
+ return jwt.payload;
103
+ }
104
+ verifyBodyAndUrl(payload, request) {
105
+ const p = payload;
98
106
  if (request.url !== void 0 && p.sub !== request.url) {
99
107
  throw new SignatureError(`invalid subject: ${p.sub}, want: ${request.url}`);
100
108
  }
@@ -103,7 +111,6 @@ var Receiver = class {
103
111
  if (p.body.replace(padding, "") !== bodyHash.replace(padding, "")) {
104
112
  throw new SignatureError(`body hash does not match, want: ${p.body}, got: ${bodyHash}`);
105
113
  }
106
- return true;
107
114
  }
108
115
  };
109
116
 
@@ -254,7 +261,7 @@ var HttpClient = class {
254
261
  attempts: 1,
255
262
  backoff: () => 0
256
263
  } : {
257
- attempts: config.retry?.retries ? config.retry.retries + 1 : 5,
264
+ attempts: config.retry?.retries ?? 5,
258
265
  backoff: config.retry?.backoff ?? ((retryCount) => Math.exp(retryCount) * 50)
259
266
  };
260
267
  }
@@ -299,13 +306,15 @@ var HttpClient = class {
299
306
  const [url, requestOptions] = this.processRequest(request);
300
307
  let response = void 0;
301
308
  let error = void 0;
302
- for (let index = 0; index < this.retry.attempts; index++) {
309
+ for (let index = 0; index <= this.retry.attempts; index++) {
303
310
  try {
304
311
  response = await fetch(url.toString(), requestOptions);
305
312
  break;
306
313
  } catch (error_) {
307
314
  error = error_;
308
- await new Promise((r) => setTimeout(r, this.retry.backoff(index)));
315
+ if (index < this.retry.attempts) {
316
+ await new Promise((r) => setTimeout(r, this.retry.backoff(index)));
317
+ }
309
318
  }
310
319
  }
311
320
  if (!response) {
@@ -402,37 +411,6 @@ var setupAnalytics = (analytics, providerApiKey, providerBaseUrl, provider) => {
402
411
  }
403
412
  }
404
413
  };
405
- var upstash = () => {
406
- return {
407
- owner: "upstash",
408
- baseUrl: "https://qstash.upstash.io/llm",
409
- token: "",
410
- organization: void 0
411
- };
412
- };
413
- var openai = ({
414
- token,
415
- organization
416
- }) => {
417
- return {
418
- token,
419
- owner: "openai",
420
- baseUrl: "https://api.openai.com",
421
- organization
422
- };
423
- };
424
- var custom = ({
425
- baseUrl,
426
- token
427
- }) => {
428
- const trimmedBaseUrl = baseUrl.replace(/\/(v1\/)?chat\/completions$/, "");
429
- return {
430
- token,
431
- owner: "custom",
432
- baseUrl: trimmedBaseUrl,
433
- organization: void 0
434
- };
435
- };
436
414
 
437
415
  // src/client/llm/chat.ts
438
416
  var Chat = class _Chat {
@@ -508,7 +486,6 @@ var Chat = class _Chat {
508
486
  * @param request ChatRequest with messages
509
487
  * @returns Chat completion or stream
510
488
  */
511
- // eslint-disable-next-line @typescript-eslint/require-await
512
489
  createThirdParty = async (request) => {
513
490
  const { baseUrl, token, owner, organization } = request.provider;
514
491
  if (owner === "upstash")
@@ -569,63 +546,6 @@ var Chat = class _Chat {
569
546
  };
570
547
  };
571
548
 
572
- // src/client/llm/utils.ts
573
- function appendLLMOptionsIfNeeded(request, headers, http) {
574
- if (!request.api)
575
- return;
576
- const provider = request.api.provider;
577
- const analytics = request.api.analytics;
578
- if (provider?.owner === "upstash") {
579
- handleUpstashProvider(request, headers, http, analytics);
580
- return;
581
- }
582
- if (!("provider" in request.api))
583
- return;
584
- const { baseUrl, token } = validateProviderConfig(provider);
585
- const analyticsConfig = analytics ? setupAnalytics({ name: analytics.name, token: analytics.token }, token, baseUrl, "custom") : void 0;
586
- if (analyticsConfig) {
587
- setAnalyticsHeaders(headers, analyticsConfig);
588
- request.url = analyticsConfig.baseURL;
589
- } else {
590
- request.url = `${baseUrl}/v1/chat/completions`;
591
- headers.set("Authorization", `Bearer ${token}`);
592
- }
593
- }
594
- function handleUpstashProvider(request, headers, http, analytics) {
595
- if (analytics) {
596
- const analyticsConfig = setupAnalytics(
597
- { name: analytics.name, token: analytics.token },
598
- //@ts-expect-error hacky way to get bearer token
599
- String(http.authorization).split("Bearer ")[1],
600
- request.api?.provider?.baseUrl,
601
- "upstash"
602
- );
603
- setAnalyticsHeaders(headers, analyticsConfig);
604
- request.url = analyticsConfig.baseURL;
605
- } else {
606
- request.api = { name: "llm" };
607
- }
608
- }
609
- function validateProviderConfig(provider) {
610
- if (!provider?.baseUrl)
611
- throw new Error("baseUrl cannot be empty or undefined!");
612
- if (!provider.token)
613
- throw new Error("token cannot be empty or undefined!");
614
- return { baseUrl: provider.baseUrl, token: provider.token };
615
- }
616
- function setAnalyticsHeaders(headers, analyticsConfig) {
617
- headers.set("Helicone-Auth", analyticsConfig.defaultHeaders?.["Helicone-Auth"] ?? "");
618
- headers.set("Authorization", analyticsConfig.defaultHeaders?.Authorization ?? "");
619
- if (analyticsConfig.defaultHeaders?.["Helicone-Target-Url"]) {
620
- headers.set("Helicone-Target-Url", analyticsConfig.defaultHeaders["Helicone-Target-Url"]);
621
- }
622
- }
623
- function ensureCallbackPresent(request) {
624
- if (request.api?.name === "llm" && !request.callback) {
625
- throw new TypeError("Callback cannot be undefined when using LLM");
626
- }
627
- }
628
-
629
549
  // src/client/messages.ts
630
550
  var Messages = class {
631
551
  http;
@@ -674,6 +594,160 @@ var Messages = class {
674
594
  }
675
595
  };
676
596
 
597
+ // src/client/api/base.ts
598
+ var BaseProvider = class {
599
+ baseUrl;
600
+ token;
601
+ owner;
602
+ constructor(baseUrl, token, owner) {
603
+ this.baseUrl = baseUrl;
604
+ this.token = token;
605
+ this.owner = owner;
606
+ }
607
+ getUrl() {
608
+ return `${this.baseUrl}/${this.getRoute().join("/")}`;
609
+ }
610
+ };
611
+
612
+ // src/client/api/llm.ts
613
+ var LLMProvider = class extends BaseProvider {
614
+ apiKind = "llm";
615
+ organization;
616
+ constructor(baseUrl, token, owner, organization) {
617
+ super(baseUrl, token, owner);
618
+ this.organization = organization;
619
+ }
620
+ getRoute() {
621
+ return this.owner === "anthropic" ? ["v1", "messages"] : ["v1", "chat", "completions"];
622
+ }
623
+ getHeaders(options) {
624
+ if (this.owner === "upstash" && !options.analytics) {
625
+ return {};
626
+ }
627
+ const header = this.owner === "anthropic" ? "x-api-key" : "authorization";
628
+ const headerValue = this.owner === "anthropic" ? this.token : `Bearer ${this.token}`;
629
+ const headers = { [header]: headerValue };
630
+ if (this.owner === "openai" && this.organization) {
631
+ headers["OpenAI-Organization"] = this.organization;
632
+ }
633
+ return headers;
634
+ }
635
+ /**
636
+ * Checks if callback exists and adds analytics in place if it's set.
637
+ *
638
+ * @param request
639
+ * @param options
640
+ */
641
+ onFinish(providerInfo, options) {
642
+ if (options.analytics) {
643
+ return updateWithAnalytics(providerInfo, options.analytics);
644
+ }
645
+ return providerInfo;
646
+ }
647
+ };
648
+ var upstash = () => {
649
+ return new LLMProvider("https://qstash.upstash.io/llm", "", "upstash");
650
+ };
651
+ var openai = ({
652
+ token,
653
+ organization
654
+ }) => {
655
+ return new LLMProvider("https://api.openai.com", token, "openai", organization);
656
+ };
657
+ var anthropic = ({ token }) => {
658
+ return new LLMProvider("https://api.anthropic.com", token, "anthropic");
659
+ };
660
+ var custom = ({
661
+ baseUrl,
662
+ token
663
+ }) => {
664
+ const trimmedBaseUrl = baseUrl.replace(/\/(v1\/)?chat\/completions$/, "");
665
+ return new LLMProvider(trimmedBaseUrl, token, "custom");
666
+ };
667
+
668
+ // src/client/api/utils.ts
669
+ var getProviderInfo = (api, upstashToken) => {
670
+ const { name, provider, ...parameters } = api;
671
+ const finalProvider = provider ?? upstash();
672
+ if (finalProvider.owner === "upstash" && !finalProvider.token) {
673
+ finalProvider.token = upstashToken;
674
+ }
675
+ if (!finalProvider.baseUrl)
676
+ throw new TypeError("baseUrl cannot be empty or undefined!");
677
+ if (!finalProvider.token)
678
+ throw new TypeError("token cannot be empty or undefined!");
679
+ if (finalProvider.apiKind !== name) {
680
+ throw new TypeError(
681
+ `Unexpected api name. Expected '${finalProvider.apiKind}', received ${name}`
682
+ );
683
+ }
684
+ const providerInfo = {
685
+ url: finalProvider.getUrl(),
686
+ baseUrl: finalProvider.baseUrl,
687
+ route: finalProvider.getRoute(),
688
+ appendHeaders: finalProvider.getHeaders(parameters),
689
+ owner: finalProvider.owner
690
+ };
691
+ return finalProvider.onFinish(providerInfo, parameters);
692
+ };
693
+ var processApi = (request, upstashToken) => {
694
+ if (!request.api) {
695
+ return request;
696
+ }
697
+ const { url, appendHeaders, owner } = getProviderInfo(request.api, upstashToken);
698
+ if (request.api.name === "llm") {
699
+ const callback = request.callback;
700
+ if (!callback) {
701
+ throw new TypeError("Callback cannot be undefined when using LLM api.");
702
+ }
703
+ return {
704
+ ...request,
705
+ // @ts-expect-error undici header conflict
706
+ headers: new Headers({
707
+ ...request.headers,
708
+ ...appendHeaders
709
+ }),
710
+ ...owner === "upstash" && !request.api.analytics ? { api: { name: "llm" }, url: void 0, callback } : { url, api: void 0 }
711
+ };
712
+ } else {
713
+ return {
714
+ ...request,
715
+ // @ts-expect-error undici header conflict
716
+ headers: new Headers({
717
+ ...request.headers,
718
+ ...appendHeaders
719
+ }),
720
+ url,
721
+ api: void 0
722
+ };
723
+ }
724
+ };
725
+ function updateWithAnalytics(providerInfo, analytics) {
726
+ switch (analytics.name) {
727
+ case "helicone": {
728
+ providerInfo.appendHeaders["Helicone-Auth"] = `Bearer ${analytics.token}`;
729
+ if (providerInfo.owner === "upstash") {
730
+ updateProviderInfo(providerInfo, "https://qstash.helicone.ai", [
731
+ "llm",
732
+ ...providerInfo.route
733
+ ]);
734
+ } else {
735
+ providerInfo.appendHeaders["Helicone-Target-Url"] = providerInfo.baseUrl;
736
+ updateProviderInfo(providerInfo, "https://gateway.helicone.ai", providerInfo.route);
737
+ }
738
+ return providerInfo;
739
+ }
740
+ default: {
741
+ throw new Error("Unknown analytics provider");
742
+ }
743
+ }
744
+ }
745
+ function updateProviderInfo(providerInfo, baseUrl, route) {
746
+ providerInfo.baseUrl = baseUrl;
747
+ providerInfo.route = route;
748
+ providerInfo.url = `${baseUrl}/${route.join("/")}`;
749
+ }
750
+
677
751
  // src/client/utils.ts
678
752
  var isIgnoredHeader = (header) => {
679
753
  const lowerCaseHeader = header.toLowerCase();
@@ -706,7 +780,7 @@ function processHeaders(request) {
706
780
  if (request.deduplicationId !== void 0) {
707
781
  headers.set("Upstash-Deduplication-Id", request.deduplicationId);
708
782
  }
709
- if (request.contentBasedDeduplication !== void 0) {
783
+ if (request.contentBasedDeduplication) {
710
784
  headers.set("Upstash-Content-Based-Deduplication", "true");
711
785
  }
712
786
  if (request.retries !== void 0) {
@@ -728,7 +802,16 @@ function processHeaders(request) {
728
802
  return headers;
729
803
  }
730
804
  function getRequestPath(request) {
731
- return request.url ?? request.urlGroup ?? request.topic ?? `api/${request.api?.name}`;
805
+ const nonApiPath = request.url ?? request.urlGroup ?? request.topic;
806
+ if (nonApiPath)
807
+ return nonApiPath;
808
+ if (request.api?.name === "llm")
809
+ return `api/llm`;
810
+ if (request.api?.name === "email") {
811
+ const providerInfo = getProviderInfo(request.api, "not-needed");
812
+ return providerInfo.baseUrl;
813
+ }
814
+ throw new QstashError(`Failed to infer request path for ${JSON.stringify(request)}`);
732
815
  }
733
816
  function decodeBase64(base64) {
734
817
  try {
@@ -736,10 +819,18 @@ function decodeBase64(base64) {
736
819
  const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
737
820
  return new TextDecoder().decode(intArray);
738
821
  } catch (error) {
739
- console.warn(
740
- `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
741
- );
742
- return atob(base64);
822
+ try {
823
+ const result = atob(base64);
824
+ console.warn(
825
+ `Upstash QStash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
826
+ );
827
+ return result;
828
+ } catch (error2) {
829
+ console.warn(
830
+ `Upstash QStash: Failed to decode base64 "${base64}" with atob. Returning it as it is. ${error2}`
831
+ );
832
+ return base64;
833
+ }
743
834
  }
744
835
  }
745
836
 
@@ -830,11 +921,12 @@ var Queue = class {
830
921
  async enqueueJSON(request) {
831
922
  const headers = prefixHeaders(new Headers(request.headers));
832
923
  headers.set("Content-Type", "application/json");
833
- ensureCallbackPresent(request);
834
- appendLLMOptionsIfNeeded(request, headers, this.http);
924
+ request.headers = headers;
925
+ const upstashToken = String(this.http.authorization).split("Bearer ")[1];
926
+ const nonApiRequest = processApi(request, upstashToken);
835
927
  const response = await this.enqueue({
836
- ...request,
837
- body: JSON.stringify(request.body),
928
+ ...nonApiRequest,
929
+ body: JSON.stringify(nonApiRequest.body),
838
930
  headers
839
931
  });
840
932
  return response;
@@ -914,6 +1006,9 @@ var Schedules = class {
914
1006
  if (request.scheduleId !== void 0) {
915
1007
  headers.set("Upstash-Schedule-Id", request.scheduleId);
916
1008
  }
1009
+ if (request.queueName !== void 0) {
1010
+ headers.set("Upstash-Queue-Name", request.queueName);
1011
+ }
917
1012
  return await this.http.request({
918
1013
  method: "POST",
919
1014
  headers,
@@ -1160,12 +1255,12 @@ var Client = class {
1160
1255
  async publishJSON(request) {
1161
1256
  const headers = prefixHeaders(new Headers(request.headers));
1162
1257
  headers.set("Content-Type", "application/json");
1163
- ensureCallbackPresent(request);
1164
- appendLLMOptionsIfNeeded(request, headers, this.http);
1258
+ request.headers = headers;
1259
+ const upstashToken = String(this.http.authorization).split("Bearer ")[1];
1260
+ const nonApiRequest = processApi(request, upstashToken);
1165
1261
  const response = await this.publish({
1166
- ...request,
1167
- headers,
1168
- body: JSON.stringify(request.body)
1262
+ ...nonApiRequest,
1263
+ body: JSON.stringify(nonApiRequest.body)
1169
1264
  });
1170
1265
  return response;
1171
1266
  }
@@ -1199,16 +1294,17 @@ var Client = class {
1199
1294
  * Batch publish messages to QStash, serializing each body to JSON.
1200
1295
  */
1201
1296
  async batchJSON(request) {
1202
- for (const message of request) {
1297
+ const batchPayload = request.map((message) => {
1203
1298
  if ("body" in message) {
1204
1299
  message.body = JSON.stringify(message.body);
1205
1300
  }
1206
1301
  message.headers = new Headers(message.headers);
1207
- ensureCallbackPresent(message);
1208
- appendLLMOptionsIfNeeded(message, message.headers, this.http);
1209
- message.headers.set("Content-Type", "application/json");
1210
- }
1211
- const response = await this.batch(request);
1302
+ const upstashToken = String(this.http.authorization).split("Bearer ")[1];
1303
+ const nonApiMessage = processApi(message, upstashToken);
1304
+ nonApiMessage.headers.set("Content-Type", "application/json");
1305
+ return nonApiMessage;
1306
+ });
1307
+ const response = await this.batch(batchPayload);
1212
1308
  return response;
1213
1309
  }
1214
1310
  /**
@@ -1263,6 +1359,33 @@ var Client = class {
1263
1359
  };
1264
1360
  }
1265
1361
  };
1362
+
1363
+ // src/client/api/email.ts
1364
+ var EmailProvider = class extends BaseProvider {
1365
+ apiKind = "email";
1366
+ batch;
1367
+ constructor(baseUrl, token, owner, batch) {
1368
+ super(baseUrl, token, owner);
1369
+ this.batch = batch;
1370
+ }
1371
+ getRoute() {
1372
+ return this.batch ? ["emails", "batch"] : ["emails"];
1373
+ }
1374
+ getHeaders(_options) {
1375
+ return {
1376
+ "upstash-forward-authorization": `Bearer ${this.token}`
1377
+ };
1378
+ }
1379
+ onFinish(providerInfo, _options) {
1380
+ return providerInfo;
1381
+ }
1382
+ };
1383
+ var resend = ({
1384
+ token,
1385
+ batch = false
1386
+ }) => {
1387
+ return new EmailProvider("https://api.resend.com", token, "resend", batch);
1388
+ };
1266
1389
  // Annotate the CommonJS export names for ESM import in node:
1267
1390
  0 && (module.exports = {
1268
1391
  Chat,
@@ -1278,10 +1401,12 @@ var Client = class {
1278
1401
  Schedules,
1279
1402
  SignatureError,
1280
1403
  UrlGroups,
1404
+ anthropic,
1281
1405
  custom,
1282
1406
  decodeBase64,
1283
1407
  formatWorkflowError,
1284
1408
  openai,
1409
+ resend,
1285
1410
  setupAnalytics,
1286
1411
  upstash
1287
1412
  });
package/index.mjs CHANGED
@@ -1,4 +1,6 @@
1
- import "./chunk-CIVGPRQN.mjs";
1
+ import {
2
+ resend
3
+ } from "./chunk-X4MWA7DF.mjs";
2
4
  import {
3
5
  Chat,
4
6
  Client,
@@ -13,13 +15,14 @@ import {
13
15
  Schedules,
14
16
  SignatureError,
15
17
  UrlGroups,
18
+ anthropic,
16
19
  custom,
17
20
  decodeBase64,
18
21
  formatWorkflowError,
19
22
  openai,
20
23
  setupAnalytics,
21
24
  upstash
22
- } from "./chunk-Q6E5NF42.mjs";
25
+ } from "./chunk-Y52A3KZT.mjs";
23
26
  export {
24
27
  Chat,
25
28
  Client,
@@ -34,10 +37,12 @@ export {
34
37
  Schedules,
35
38
  SignatureError,
36
39
  UrlGroups,
40
+ anthropic,
37
41
  custom,
38
42
  decodeBase64,
39
43
  formatWorkflowError,
40
44
  openai,
45
+ resend,
41
46
  setupAnalytics,
42
47
  upstash
43
48
  };
package/nextjs.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { NextApiHandler } from 'next';
2
2
  import { NextRequest, NextFetchEvent } from 'next/server';
3
- import { a2 as RouteFunction, a3 as WorkflowServeOptions } from './client-DEZq0-qk.mjs';
3
+ import { a3 as RouteFunction, a4 as WorkflowServeOptions } from './client-RORrka04.mjs';
4
4
  import 'neverthrow';
5
5
 
6
6
  type VerifySignatureConfig = {
package/nextjs.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { NextApiHandler } from 'next';
2
2
  import { NextRequest, NextFetchEvent } from 'next/server';
3
- import { a2 as RouteFunction, a3 as WorkflowServeOptions } from './client-DEZq0-qk.js';
3
+ import { a3 as RouteFunction, a4 as WorkflowServeOptions } from './client-RORrka04.js';
4
4
  import 'neverthrow';
5
5
 
6
6
  type VerifySignatureConfig = {