@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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Receiver,
3
3
  serve
4
- } from "./chunk-Q6E5NF42.mjs";
4
+ } from "./chunk-Y52A3KZT.mjs";
5
5
 
6
6
  // node_modules/defu/dist/defu.mjs
7
7
  function isPlainObject(value) {
@@ -0,0 +1,34 @@
1
+ import {
2
+ BaseProvider
3
+ } from "./chunk-Y52A3KZT.mjs";
4
+
5
+ // src/client/api/email.ts
6
+ var EmailProvider = class extends BaseProvider {
7
+ apiKind = "email";
8
+ batch;
9
+ constructor(baseUrl, token, owner, batch) {
10
+ super(baseUrl, token, owner);
11
+ this.batch = batch;
12
+ }
13
+ getRoute() {
14
+ return this.batch ? ["emails", "batch"] : ["emails"];
15
+ }
16
+ getHeaders(_options) {
17
+ return {
18
+ "upstash-forward-authorization": `Bearer ${this.token}`
19
+ };
20
+ }
21
+ onFinish(providerInfo, _options) {
22
+ return providerInfo;
23
+ }
24
+ };
25
+ var resend = ({
26
+ token,
27
+ batch = false
28
+ }) => {
29
+ return new EmailProvider("https://api.resend.com", token, "resend", batch);
30
+ };
31
+
32
+ export {
33
+ resend
34
+ };
@@ -24,11 +24,14 @@ var Receiver = class {
24
24
  * If that fails, the signature is invalid and a `SignatureError` is thrown.
25
25
  */
26
26
  async verify(request) {
27
- const isValid = await this.verifyWithKey(this.currentSigningKey, request);
28
- if (isValid) {
29
- return true;
27
+ let payload;
28
+ try {
29
+ payload = await this.verifyWithKey(this.currentSigningKey, request);
30
+ } catch {
31
+ payload = await this.verifyWithKey(this.nextSigningKey, request);
30
32
  }
31
- return this.verifyWithKey(this.nextSigningKey, request);
33
+ this.verifyBodyAndUrl(payload, request);
34
+ return true;
32
35
  }
33
36
  /**
34
37
  * Verify signature with a specific signing key
@@ -40,7 +43,10 @@ var Receiver = class {
40
43
  }).catch((error) => {
41
44
  throw new SignatureError(error.message);
42
45
  });
43
- const p = jwt.payload;
46
+ return jwt.payload;
47
+ }
48
+ verifyBodyAndUrl(payload, request) {
49
+ const p = payload;
44
50
  if (request.url !== void 0 && p.sub !== request.url) {
45
51
  throw new SignatureError(`invalid subject: ${p.sub}, want: ${request.url}`);
46
52
  }
@@ -49,7 +55,6 @@ var Receiver = class {
49
55
  if (p.body.replace(padding, "") !== bodyHash.replace(padding, "")) {
50
56
  throw new SignatureError(`body hash does not match, want: ${p.body}, got: ${bodyHash}`);
51
57
  }
52
- return true;
53
58
  }
54
59
  };
55
60
 
@@ -200,7 +205,7 @@ var HttpClient = class {
200
205
  attempts: 1,
201
206
  backoff: () => 0
202
207
  } : {
203
- attempts: config.retry?.retries ? config.retry.retries + 1 : 5,
208
+ attempts: config.retry?.retries ?? 5,
204
209
  backoff: config.retry?.backoff ?? ((retryCount) => Math.exp(retryCount) * 50)
205
210
  };
206
211
  }
@@ -245,13 +250,15 @@ var HttpClient = class {
245
250
  const [url, requestOptions] = this.processRequest(request);
246
251
  let response = void 0;
247
252
  let error = void 0;
248
- for (let index = 0; index < this.retry.attempts; index++) {
253
+ for (let index = 0; index <= this.retry.attempts; index++) {
249
254
  try {
250
255
  response = await fetch(url.toString(), requestOptions);
251
256
  break;
252
257
  } catch (error_) {
253
258
  error = error_;
254
- await new Promise((r) => setTimeout(r, this.retry.backoff(index)));
259
+ if (index < this.retry.attempts) {
260
+ await new Promise((r) => setTimeout(r, this.retry.backoff(index)));
261
+ }
255
262
  }
256
263
  }
257
264
  if (!response) {
@@ -348,37 +355,6 @@ var setupAnalytics = (analytics, providerApiKey, providerBaseUrl, provider) => {
348
355
  }
349
356
  }
350
357
  };
351
- var upstash = () => {
352
- return {
353
- owner: "upstash",
354
- baseUrl: "https://qstash.upstash.io/llm",
355
- token: "",
356
- organization: void 0
357
- };
358
- };
359
- var openai = ({
360
- token,
361
- organization
362
- }) => {
363
- return {
364
- token,
365
- owner: "openai",
366
- baseUrl: "https://api.openai.com",
367
- organization
368
- };
369
- };
370
- var custom = ({
371
- baseUrl,
372
- token
373
- }) => {
374
- const trimmedBaseUrl = baseUrl.replace(/\/(v1\/)?chat\/completions$/, "");
375
- return {
376
- token,
377
- owner: "custom",
378
- baseUrl: trimmedBaseUrl,
379
- organization: void 0
380
- };
381
- };
382
358
 
383
359
  // src/client/llm/chat.ts
384
360
  var Chat = class _Chat {
@@ -454,7 +430,6 @@ var Chat = class _Chat {
454
430
  * @param request ChatRequest with messages
455
431
  * @returns Chat completion or stream
456
432
  */
457
- // eslint-disable-next-line @typescript-eslint/require-await
458
433
  createThirdParty = async (request) => {
459
434
  const { baseUrl, token, owner, organization } = request.provider;
460
435
  if (owner === "upstash")
@@ -515,63 +490,6 @@ var Chat = class _Chat {
515
490
  };
516
491
  };
517
492
 
518
- // src/client/llm/utils.ts
519
- function appendLLMOptionsIfNeeded(request, headers, http) {
520
- if (!request.api)
521
- return;
522
- const provider = request.api.provider;
523
- const analytics = request.api.analytics;
524
- if (provider?.owner === "upstash") {
525
- handleUpstashProvider(request, headers, http, analytics);
526
- return;
527
- }
528
- if (!("provider" in request.api))
529
- return;
530
- const { baseUrl, token } = validateProviderConfig(provider);
531
- const analyticsConfig = analytics ? setupAnalytics({ name: analytics.name, token: analytics.token }, token, baseUrl, "custom") : void 0;
532
- if (analyticsConfig) {
533
- setAnalyticsHeaders(headers, analyticsConfig);
534
- request.url = analyticsConfig.baseURL;
535
- } else {
536
- request.url = `${baseUrl}/v1/chat/completions`;
537
- headers.set("Authorization", `Bearer ${token}`);
538
- }
539
- }
540
- function handleUpstashProvider(request, headers, http, analytics) {
541
- if (analytics) {
542
- const analyticsConfig = setupAnalytics(
543
- { name: analytics.name, token: analytics.token },
544
- //@ts-expect-error hacky way to get bearer token
545
- String(http.authorization).split("Bearer ")[1],
546
- request.api?.provider?.baseUrl,
547
- "upstash"
548
- );
549
- setAnalyticsHeaders(headers, analyticsConfig);
550
- request.url = analyticsConfig.baseURL;
551
- } else {
552
- request.api = { name: "llm" };
553
- }
554
- }
555
- function validateProviderConfig(provider) {
556
- if (!provider?.baseUrl)
557
- throw new Error("baseUrl cannot be empty or undefined!");
558
- if (!provider.token)
559
- throw new Error("token cannot be empty or undefined!");
560
- return { baseUrl: provider.baseUrl, token: provider.token };
561
- }
562
- function setAnalyticsHeaders(headers, analyticsConfig) {
563
- headers.set("Helicone-Auth", analyticsConfig.defaultHeaders?.["Helicone-Auth"] ?? "");
564
- headers.set("Authorization", analyticsConfig.defaultHeaders?.Authorization ?? "");
565
- if (analyticsConfig.defaultHeaders?.["Helicone-Target-Url"]) {
566
- headers.set("Helicone-Target-Url", analyticsConfig.defaultHeaders["Helicone-Target-Url"]);
567
- }
568
- }
569
- function ensureCallbackPresent(request) {
570
- if (request.api?.name === "llm" && !request.callback) {
571
- throw new TypeError("Callback cannot be undefined when using LLM");
572
- }
573
- }
574
-
575
493
  // src/client/messages.ts
576
494
  var Messages = class {
577
495
  http;
@@ -620,6 +538,160 @@ var Messages = class {
620
538
  }
621
539
  };
622
540
 
541
+ // src/client/api/base.ts
542
+ var BaseProvider = class {
543
+ baseUrl;
544
+ token;
545
+ owner;
546
+ constructor(baseUrl, token, owner) {
547
+ this.baseUrl = baseUrl;
548
+ this.token = token;
549
+ this.owner = owner;
550
+ }
551
+ getUrl() {
552
+ return `${this.baseUrl}/${this.getRoute().join("/")}`;
553
+ }
554
+ };
555
+
556
+ // src/client/api/llm.ts
557
+ var LLMProvider = class extends BaseProvider {
558
+ apiKind = "llm";
559
+ organization;
560
+ constructor(baseUrl, token, owner, organization) {
561
+ super(baseUrl, token, owner);
562
+ this.organization = organization;
563
+ }
564
+ getRoute() {
565
+ return this.owner === "anthropic" ? ["v1", "messages"] : ["v1", "chat", "completions"];
566
+ }
567
+ getHeaders(options) {
568
+ if (this.owner === "upstash" && !options.analytics) {
569
+ return {};
570
+ }
571
+ const header = this.owner === "anthropic" ? "x-api-key" : "authorization";
572
+ const headerValue = this.owner === "anthropic" ? this.token : `Bearer ${this.token}`;
573
+ const headers = { [header]: headerValue };
574
+ if (this.owner === "openai" && this.organization) {
575
+ headers["OpenAI-Organization"] = this.organization;
576
+ }
577
+ return headers;
578
+ }
579
+ /**
580
+ * Checks if callback exists and adds analytics in place if it's set.
581
+ *
582
+ * @param request
583
+ * @param options
584
+ */
585
+ onFinish(providerInfo, options) {
586
+ if (options.analytics) {
587
+ return updateWithAnalytics(providerInfo, options.analytics);
588
+ }
589
+ return providerInfo;
590
+ }
591
+ };
592
+ var upstash = () => {
593
+ return new LLMProvider("https://qstash.upstash.io/llm", "", "upstash");
594
+ };
595
+ var openai = ({
596
+ token,
597
+ organization
598
+ }) => {
599
+ return new LLMProvider("https://api.openai.com", token, "openai", organization);
600
+ };
601
+ var anthropic = ({ token }) => {
602
+ return new LLMProvider("https://api.anthropic.com", token, "anthropic");
603
+ };
604
+ var custom = ({
605
+ baseUrl,
606
+ token
607
+ }) => {
608
+ const trimmedBaseUrl = baseUrl.replace(/\/(v1\/)?chat\/completions$/, "");
609
+ return new LLMProvider(trimmedBaseUrl, token, "custom");
610
+ };
611
+
612
+ // src/client/api/utils.ts
613
+ var getProviderInfo = (api, upstashToken) => {
614
+ const { name, provider, ...parameters } = api;
615
+ const finalProvider = provider ?? upstash();
616
+ if (finalProvider.owner === "upstash" && !finalProvider.token) {
617
+ finalProvider.token = upstashToken;
618
+ }
619
+ if (!finalProvider.baseUrl)
620
+ throw new TypeError("baseUrl cannot be empty or undefined!");
621
+ if (!finalProvider.token)
622
+ throw new TypeError("token cannot be empty or undefined!");
623
+ if (finalProvider.apiKind !== name) {
624
+ throw new TypeError(
625
+ `Unexpected api name. Expected '${finalProvider.apiKind}', received ${name}`
626
+ );
627
+ }
628
+ const providerInfo = {
629
+ url: finalProvider.getUrl(),
630
+ baseUrl: finalProvider.baseUrl,
631
+ route: finalProvider.getRoute(),
632
+ appendHeaders: finalProvider.getHeaders(parameters),
633
+ owner: finalProvider.owner
634
+ };
635
+ return finalProvider.onFinish(providerInfo, parameters);
636
+ };
637
+ var processApi = (request, upstashToken) => {
638
+ if (!request.api) {
639
+ return request;
640
+ }
641
+ const { url, appendHeaders, owner } = getProviderInfo(request.api, upstashToken);
642
+ if (request.api.name === "llm") {
643
+ const callback = request.callback;
644
+ if (!callback) {
645
+ throw new TypeError("Callback cannot be undefined when using LLM api.");
646
+ }
647
+ return {
648
+ ...request,
649
+ // @ts-expect-error undici header conflict
650
+ headers: new Headers({
651
+ ...request.headers,
652
+ ...appendHeaders
653
+ }),
654
+ ...owner === "upstash" && !request.api.analytics ? { api: { name: "llm" }, url: void 0, callback } : { url, api: void 0 }
655
+ };
656
+ } else {
657
+ return {
658
+ ...request,
659
+ // @ts-expect-error undici header conflict
660
+ headers: new Headers({
661
+ ...request.headers,
662
+ ...appendHeaders
663
+ }),
664
+ url,
665
+ api: void 0
666
+ };
667
+ }
668
+ };
669
+ function updateWithAnalytics(providerInfo, analytics) {
670
+ switch (analytics.name) {
671
+ case "helicone": {
672
+ providerInfo.appendHeaders["Helicone-Auth"] = `Bearer ${analytics.token}`;
673
+ if (providerInfo.owner === "upstash") {
674
+ updateProviderInfo(providerInfo, "https://qstash.helicone.ai", [
675
+ "llm",
676
+ ...providerInfo.route
677
+ ]);
678
+ } else {
679
+ providerInfo.appendHeaders["Helicone-Target-Url"] = providerInfo.baseUrl;
680
+ updateProviderInfo(providerInfo, "https://gateway.helicone.ai", providerInfo.route);
681
+ }
682
+ return providerInfo;
683
+ }
684
+ default: {
685
+ throw new Error("Unknown analytics provider");
686
+ }
687
+ }
688
+ }
689
+ function updateProviderInfo(providerInfo, baseUrl, route) {
690
+ providerInfo.baseUrl = baseUrl;
691
+ providerInfo.route = route;
692
+ providerInfo.url = `${baseUrl}/${route.join("/")}`;
693
+ }
694
+
623
695
  // src/client/utils.ts
624
696
  var isIgnoredHeader = (header) => {
625
697
  const lowerCaseHeader = header.toLowerCase();
@@ -652,7 +724,7 @@ function processHeaders(request) {
652
724
  if (request.deduplicationId !== void 0) {
653
725
  headers.set("Upstash-Deduplication-Id", request.deduplicationId);
654
726
  }
655
- if (request.contentBasedDeduplication !== void 0) {
727
+ if (request.contentBasedDeduplication) {
656
728
  headers.set("Upstash-Content-Based-Deduplication", "true");
657
729
  }
658
730
  if (request.retries !== void 0) {
@@ -674,7 +746,16 @@ function processHeaders(request) {
674
746
  return headers;
675
747
  }
676
748
  function getRequestPath(request) {
677
- return request.url ?? request.urlGroup ?? request.topic ?? `api/${request.api?.name}`;
749
+ const nonApiPath = request.url ?? request.urlGroup ?? request.topic;
750
+ if (nonApiPath)
751
+ return nonApiPath;
752
+ if (request.api?.name === "llm")
753
+ return `api/llm`;
754
+ if (request.api?.name === "email") {
755
+ const providerInfo = getProviderInfo(request.api, "not-needed");
756
+ return providerInfo.baseUrl;
757
+ }
758
+ throw new QstashError(`Failed to infer request path for ${JSON.stringify(request)}`);
678
759
  }
679
760
  var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
680
761
  var NANOID_LENGTH = 21;
@@ -687,10 +768,18 @@ function decodeBase64(base64) {
687
768
  const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
688
769
  return new TextDecoder().decode(intArray);
689
770
  } catch (error) {
690
- console.warn(
691
- `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
692
- );
693
- return atob(base64);
771
+ try {
772
+ const result = atob(base64);
773
+ console.warn(
774
+ `Upstash QStash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
775
+ );
776
+ return result;
777
+ } catch (error2) {
778
+ console.warn(
779
+ `Upstash QStash: Failed to decode base64 "${base64}" with atob. Returning it as it is. ${error2}`
780
+ );
781
+ return base64;
782
+ }
694
783
  }
695
784
  }
696
785
 
@@ -781,11 +870,12 @@ var Queue = class {
781
870
  async enqueueJSON(request) {
782
871
  const headers = prefixHeaders(new Headers(request.headers));
783
872
  headers.set("Content-Type", "application/json");
784
- ensureCallbackPresent(request);
785
- appendLLMOptionsIfNeeded(request, headers, this.http);
873
+ request.headers = headers;
874
+ const upstashToken = String(this.http.authorization).split("Bearer ")[1];
875
+ const nonApiRequest = processApi(request, upstashToken);
786
876
  const response = await this.enqueue({
787
- ...request,
788
- body: JSON.stringify(request.body),
877
+ ...nonApiRequest,
878
+ body: JSON.stringify(nonApiRequest.body),
789
879
  headers
790
880
  });
791
881
  return response;
@@ -865,6 +955,9 @@ var Schedules = class {
865
955
  if (request.scheduleId !== void 0) {
866
956
  headers.set("Upstash-Schedule-Id", request.scheduleId);
867
957
  }
958
+ if (request.queueName !== void 0) {
959
+ headers.set("Upstash-Queue-Name", request.queueName);
960
+ }
868
961
  return await this.http.request({
869
962
  method: "POST",
870
963
  headers,
@@ -1080,12 +1173,12 @@ var Client = class {
1080
1173
  async publishJSON(request) {
1081
1174
  const headers = prefixHeaders(new Headers(request.headers));
1082
1175
  headers.set("Content-Type", "application/json");
1083
- ensureCallbackPresent(request);
1084
- appendLLMOptionsIfNeeded(request, headers, this.http);
1176
+ request.headers = headers;
1177
+ const upstashToken = String(this.http.authorization).split("Bearer ")[1];
1178
+ const nonApiRequest = processApi(request, upstashToken);
1085
1179
  const response = await this.publish({
1086
- ...request,
1087
- headers,
1088
- body: JSON.stringify(request.body)
1180
+ ...nonApiRequest,
1181
+ body: JSON.stringify(nonApiRequest.body)
1089
1182
  });
1090
1183
  return response;
1091
1184
  }
@@ -1119,16 +1212,17 @@ var Client = class {
1119
1212
  * Batch publish messages to QStash, serializing each body to JSON.
1120
1213
  */
1121
1214
  async batchJSON(request) {
1122
- for (const message of request) {
1215
+ const batchPayload = request.map((message) => {
1123
1216
  if ("body" in message) {
1124
1217
  message.body = JSON.stringify(message.body);
1125
1218
  }
1126
1219
  message.headers = new Headers(message.headers);
1127
- ensureCallbackPresent(message);
1128
- appendLLMOptionsIfNeeded(message, message.headers, this.http);
1129
- message.headers.set("Content-Type", "application/json");
1130
- }
1131
- const response = await this.batch(request);
1220
+ const upstashToken = String(this.http.authorization).split("Bearer ")[1];
1221
+ const nonApiMessage = processApi(message, upstashToken);
1222
+ nonApiMessage.headers.set("Content-Type", "application/json");
1223
+ return nonApiMessage;
1224
+ });
1225
+ const response = await this.batch(batchPayload);
1132
1226
  return response;
1133
1227
  }
1134
1228
  /**
@@ -2581,11 +2675,13 @@ export {
2581
2675
  QStashWorkflowAbort,
2582
2676
  formatWorkflowError,
2583
2677
  setupAnalytics,
2678
+ Chat,
2679
+ Messages,
2680
+ BaseProvider,
2584
2681
  upstash,
2585
2682
  openai,
2683
+ anthropic,
2586
2684
  custom,
2587
- Chat,
2588
- Messages,
2589
2685
  decodeBase64,
2590
2686
  Schedules,
2591
2687
  UrlGroups,