@upstash/qstash 2.8.4 → 2.9.0-rc

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,526 @@
1
1
  // src/receiver.ts
2
2
  import * as jose from "jose";
3
3
  import crypto2 from "crypto-js";
4
+
5
+ // src/client/api/base.ts
6
+ var BaseProvider = class {
7
+ baseUrl;
8
+ token;
9
+ owner;
10
+ constructor(baseUrl, token, owner) {
11
+ this.baseUrl = baseUrl;
12
+ this.token = token;
13
+ this.owner = owner;
14
+ }
15
+ getUrl() {
16
+ return `${this.baseUrl}/${this.getRoute().join("/")}`;
17
+ }
18
+ };
19
+
20
+ // src/client/api/llm.ts
21
+ var LLMProvider = class extends BaseProvider {
22
+ apiKind = "llm";
23
+ organization;
24
+ method = "POST";
25
+ constructor(baseUrl, token, owner, organization) {
26
+ super(baseUrl, token, owner);
27
+ this.organization = organization;
28
+ }
29
+ getRoute() {
30
+ return this.owner === "anthropic" ? ["v1", "messages"] : ["v1", "chat", "completions"];
31
+ }
32
+ getHeaders(options) {
33
+ if (this.owner === "upstash" && !options.analytics) {
34
+ return { "content-type": "application/json" };
35
+ }
36
+ const header = this.owner === "anthropic" ? "x-api-key" : "authorization";
37
+ const headerValue = this.owner === "anthropic" ? this.token : `Bearer ${this.token}`;
38
+ const headers = {
39
+ [header]: headerValue,
40
+ "content-type": "application/json"
41
+ };
42
+ if (this.owner === "openai" && this.organization) {
43
+ headers["OpenAI-Organization"] = this.organization;
44
+ }
45
+ if (this.owner === "anthropic") {
46
+ headers["anthropic-version"] = "2023-06-01";
47
+ }
48
+ return headers;
49
+ }
50
+ /**
51
+ * Checks if callback exists and adds analytics in place if it's set.
52
+ *
53
+ * @param request
54
+ * @param options
55
+ */
56
+ onFinish(providerInfo, options) {
57
+ if (options.analytics) {
58
+ return updateWithAnalytics(providerInfo, options.analytics);
59
+ }
60
+ return providerInfo;
61
+ }
62
+ };
63
+ var upstash = () => {
64
+ return new LLMProvider("https://qstash.upstash.io/llm", "", "upstash");
65
+ };
66
+ var openai = ({
67
+ token,
68
+ organization
69
+ }) => {
70
+ return new LLMProvider("https://api.openai.com", token, "openai", organization);
71
+ };
72
+ var anthropic = ({ token }) => {
73
+ return new LLMProvider("https://api.anthropic.com", token, "anthropic");
74
+ };
75
+ var custom = ({
76
+ baseUrl,
77
+ token
78
+ }) => {
79
+ const trimmedBaseUrl = baseUrl.replace(/\/(v1\/)?chat\/completions$/, "");
80
+ return new LLMProvider(trimmedBaseUrl, token, "custom");
81
+ };
82
+
83
+ // src/client/api/utils.ts
84
+ var getProviderInfo = (api, upstashToken) => {
85
+ const { name, provider, ...parameters } = api;
86
+ const finalProvider = provider ?? upstash();
87
+ if (finalProvider.owner === "upstash" && !finalProvider.token) {
88
+ finalProvider.token = upstashToken;
89
+ }
90
+ if (!finalProvider.baseUrl)
91
+ throw new TypeError("baseUrl cannot be empty or undefined!");
92
+ if (!finalProvider.token)
93
+ throw new TypeError("token cannot be empty or undefined!");
94
+ if (finalProvider.apiKind !== name) {
95
+ throw new TypeError(
96
+ `Unexpected api name. Expected '${finalProvider.apiKind}', received ${name}`
97
+ );
98
+ }
99
+ const providerInfo = {
100
+ url: finalProvider.getUrl(),
101
+ baseUrl: finalProvider.baseUrl,
102
+ route: finalProvider.getRoute(),
103
+ appendHeaders: finalProvider.getHeaders(parameters),
104
+ owner: finalProvider.owner,
105
+ method: finalProvider.method
106
+ };
107
+ return finalProvider.onFinish(providerInfo, parameters);
108
+ };
109
+ var safeJoinHeaders = (headers, record) => {
110
+ const joinedHeaders = new Headers(record);
111
+ for (const [header, value] of headers.entries()) {
112
+ joinedHeaders.set(header, value);
113
+ }
114
+ return joinedHeaders;
115
+ };
116
+ var processApi = (request, headers, upstashToken) => {
117
+ if (!request.api) {
118
+ request.headers = headers;
119
+ return request;
120
+ }
121
+ const { url, appendHeaders, owner, method } = getProviderInfo(request.api, upstashToken);
122
+ if (request.api.name === "llm") {
123
+ const callback = request.callback;
124
+ if (!callback) {
125
+ throw new TypeError("Callback cannot be undefined when using LLM api.");
126
+ }
127
+ return {
128
+ ...request,
129
+ method: request.method ?? method,
130
+ headers: safeJoinHeaders(headers, appendHeaders),
131
+ ...owner === "upstash" && !request.api.analytics ? { api: { name: "llm" }, url: void 0, callback } : { url, api: void 0 }
132
+ };
133
+ } else {
134
+ return {
135
+ ...request,
136
+ method: request.method ?? method,
137
+ headers: safeJoinHeaders(headers, appendHeaders),
138
+ url,
139
+ api: void 0
140
+ };
141
+ }
142
+ };
143
+ function updateWithAnalytics(providerInfo, analytics) {
144
+ switch (analytics.name) {
145
+ case "helicone": {
146
+ providerInfo.appendHeaders["Helicone-Auth"] = `Bearer ${analytics.token}`;
147
+ if (providerInfo.owner === "upstash") {
148
+ updateProviderInfo(providerInfo, "https://qstash.helicone.ai", [
149
+ "llm",
150
+ ...providerInfo.route
151
+ ]);
152
+ } else {
153
+ providerInfo.appendHeaders["Helicone-Target-Url"] = providerInfo.baseUrl;
154
+ updateProviderInfo(providerInfo, "https://gateway.helicone.ai", providerInfo.route);
155
+ }
156
+ return providerInfo;
157
+ }
158
+ default: {
159
+ throw new Error("Unknown analytics provider");
160
+ }
161
+ }
162
+ }
163
+ function updateProviderInfo(providerInfo, baseUrl, route) {
164
+ providerInfo.baseUrl = baseUrl;
165
+ providerInfo.route = route;
166
+ providerInfo.url = `${baseUrl}/${route.join("/")}`;
167
+ }
168
+
169
+ // src/client/error.ts
170
+ var RATELIMIT_STATUS = 429;
171
+ var QstashError = class extends Error {
172
+ status;
173
+ constructor(message, status) {
174
+ super(message);
175
+ this.name = "QstashError";
176
+ this.status = status;
177
+ }
178
+ };
179
+ var QstashRatelimitError = class extends QstashError {
180
+ limit;
181
+ remaining;
182
+ reset;
183
+ constructor(args) {
184
+ super(`Exceeded burst rate limit. ${JSON.stringify(args)}`, RATELIMIT_STATUS);
185
+ this.name = "QstashRatelimitError";
186
+ this.limit = args.limit;
187
+ this.remaining = args.remaining;
188
+ this.reset = args.reset;
189
+ }
190
+ };
191
+ var QstashChatRatelimitError = class extends QstashError {
192
+ limitRequests;
193
+ limitTokens;
194
+ remainingRequests;
195
+ remainingTokens;
196
+ resetRequests;
197
+ resetTokens;
198
+ constructor(args) {
199
+ super(`Exceeded chat rate limit. ${JSON.stringify(args)}`, RATELIMIT_STATUS);
200
+ this.name = "QstashChatRatelimitError";
201
+ this.limitRequests = args["limit-requests"];
202
+ this.limitTokens = args["limit-tokens"];
203
+ this.remainingRequests = args["remaining-requests"];
204
+ this.remainingTokens = args["remaining-tokens"];
205
+ this.resetRequests = args["reset-requests"];
206
+ this.resetTokens = args["reset-tokens"];
207
+ }
208
+ };
209
+ var QstashDailyRatelimitError = class extends QstashError {
210
+ limit;
211
+ remaining;
212
+ reset;
213
+ constructor(args) {
214
+ super(`Exceeded daily rate limit. ${JSON.stringify(args)}`, RATELIMIT_STATUS);
215
+ this.name = "QstashDailyRatelimitError";
216
+ this.limit = args.limit;
217
+ this.remaining = args.remaining;
218
+ this.reset = args.reset;
219
+ }
220
+ };
221
+ var QStashWorkflowError = class extends QstashError {
222
+ constructor(message) {
223
+ super(message);
224
+ this.name = "QStashWorkflowError";
225
+ }
226
+ };
227
+ var QStashWorkflowAbort = class extends Error {
228
+ stepInfo;
229
+ stepName;
230
+ constructor(stepName, stepInfo) {
231
+ super(
232
+ `This is an Upstash Workflow error thrown after a step executes. It is expected to be raised. Make sure that you await for each step. Also, if you are using try/catch blocks, you should not wrap context.run/sleep/sleepUntil/call methods with try/catch. Aborting workflow after executing step '${stepName}'.`
233
+ );
234
+ this.name = "QStashWorkflowAbort";
235
+ this.stepName = stepName;
236
+ this.stepInfo = stepInfo;
237
+ }
238
+ };
239
+ var formatWorkflowError = (error) => {
240
+ return error instanceof Error ? {
241
+ error: error.name,
242
+ message: error.message
243
+ } : {
244
+ error: "Error",
245
+ message: "An error occured while executing workflow."
246
+ };
247
+ };
248
+
249
+ // src/client/utils.ts
250
+ var isIgnoredHeader = (header) => {
251
+ const lowerCaseHeader = header.toLowerCase();
252
+ return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
253
+ };
254
+ function prefixHeaders(headers) {
255
+ const keysToBePrefixed = [...headers.keys()].filter((key) => !isIgnoredHeader(key));
256
+ for (const key of keysToBePrefixed) {
257
+ const value = headers.get(key);
258
+ if (value !== null) {
259
+ headers.set(`Upstash-Forward-${key}`, value);
260
+ }
261
+ headers.delete(key);
262
+ }
263
+ return headers;
264
+ }
265
+ function wrapWithGlobalHeaders(headers, globalHeaders, telemetryHeaders) {
266
+ if (!globalHeaders) {
267
+ return headers;
268
+ }
269
+ const finalHeaders = new Headers(globalHeaders);
270
+ headers.forEach((value, key) => {
271
+ finalHeaders.set(key, value);
272
+ });
273
+ telemetryHeaders?.forEach((value, key) => {
274
+ if (!value)
275
+ return;
276
+ finalHeaders.append(key, value);
277
+ });
278
+ return finalHeaders;
279
+ }
280
+ function processHeaders(request) {
281
+ const headers = prefixHeaders(new Headers(request.headers));
282
+ headers.set("Upstash-Method", request.method ?? "POST");
283
+ if (request.delay !== void 0) {
284
+ if (typeof request.delay === "string") {
285
+ headers.set("Upstash-Delay", request.delay);
286
+ } else {
287
+ headers.set("Upstash-Delay", `${request.delay.toFixed(0)}s`);
288
+ }
289
+ }
290
+ if (request.notBefore !== void 0) {
291
+ headers.set("Upstash-Not-Before", request.notBefore.toFixed(0));
292
+ }
293
+ if (request.deduplicationId !== void 0) {
294
+ headers.set("Upstash-Deduplication-Id", request.deduplicationId);
295
+ }
296
+ if (request.contentBasedDeduplication) {
297
+ headers.set("Upstash-Content-Based-Deduplication", "true");
298
+ }
299
+ if (request.retries !== void 0) {
300
+ headers.set("Upstash-Retries", request.retries.toFixed(0));
301
+ }
302
+ if (request.retryDelay !== void 0) {
303
+ headers.set("Upstash-Retry-Delay", request.retryDelay);
304
+ }
305
+ if (request.callback !== void 0) {
306
+ headers.set("Upstash-Callback", request.callback);
307
+ }
308
+ if (request.failureCallback !== void 0) {
309
+ headers.set("Upstash-Failure-Callback", request.failureCallback);
310
+ }
311
+ if (request.timeout !== void 0) {
312
+ if (typeof request.timeout === "string") {
313
+ headers.set("Upstash-Timeout", request.timeout);
314
+ } else {
315
+ headers.set("Upstash-Timeout", `${request.timeout}s`);
316
+ }
317
+ }
318
+ if (request.flowControl?.key) {
319
+ const parallelism = request.flowControl.parallelism?.toString();
320
+ const rate = (request.flowControl.rate ?? request.flowControl.ratePerSecond)?.toString();
321
+ const period = typeof request.flowControl.period === "number" ? `${request.flowControl.period}s` : request.flowControl.period;
322
+ const controlValue = [
323
+ parallelism ? `parallelism=${parallelism}` : void 0,
324
+ rate ? `rate=${rate}` : void 0,
325
+ period ? `period=${period}` : void 0
326
+ ].filter(Boolean);
327
+ if (controlValue.length === 0) {
328
+ throw new QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
329
+ }
330
+ headers.set("Upstash-Flow-Control-Key", request.flowControl.key);
331
+ headers.set("Upstash-Flow-Control-Value", controlValue.join(", "));
332
+ }
333
+ if (request.label !== void 0) {
334
+ headers.set("Upstash-Label", request.label);
335
+ }
336
+ return headers;
337
+ }
338
+ function getRequestPath(request) {
339
+ const nonApiPath = request.url ?? request.urlGroup ?? request.topic;
340
+ if (nonApiPath)
341
+ return nonApiPath;
342
+ if (request.api?.name === "llm")
343
+ return `api/llm`;
344
+ if (request.api?.name === "email") {
345
+ const providerInfo = getProviderInfo(request.api, "not-needed");
346
+ return providerInfo.baseUrl;
347
+ }
348
+ throw new QstashError(`Failed to infer request path for ${JSON.stringify(request)}`);
349
+ }
350
+ var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
351
+ var NANOID_LENGTH = 21;
352
+ function nanoid() {
353
+ return [...crypto.getRandomValues(new Uint8Array(NANOID_LENGTH))].map((x) => NANOID_CHARS[x % NANOID_CHARS.length]).join("");
354
+ }
355
+ function decodeBase64(base64) {
356
+ try {
357
+ const binString = atob(base64);
358
+ const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
359
+ return new TextDecoder().decode(intArray);
360
+ } catch (error) {
361
+ try {
362
+ const result = atob(base64);
363
+ console.warn(
364
+ `Upstash QStash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
365
+ );
366
+ return result;
367
+ } catch (error2) {
368
+ console.warn(
369
+ `Upstash QStash: Failed to decode base64 "${base64}" with atob. Returning it as it is. ${error2}`
370
+ );
371
+ return base64;
372
+ }
373
+ }
374
+ }
375
+ function getRuntime() {
376
+ if (typeof process === "object" && typeof process.versions == "object" && process.versions.bun)
377
+ return `bun@${process.versions.bun}`;
378
+ if (typeof EdgeRuntime === "string")
379
+ return "edge-light";
380
+ else if (typeof process === "object" && typeof process.version === "string")
381
+ return `node@${process.version}`;
382
+ return "";
383
+ }
384
+ function getSafeEnvironment() {
385
+ return typeof process === "undefined" ? {} : process.env;
386
+ }
387
+
388
+ // src/client/multi-region/utils.ts
389
+ var VALID_REGIONS = ["EU_CENTRAL_1", "US_EAST_1"];
390
+ var DEFAULT_QSTASH_URL = "https://qstash.upstash.io";
391
+ var getRegionFromEnvironment = (environment) => {
392
+ const region = environment.QSTASH_REGION;
393
+ return normalizeRegionHeader(region);
394
+ };
395
+ function readEnvironmentVariables(environmentVariables, environment, region) {
396
+ const result = {};
397
+ for (const variable of environmentVariables) {
398
+ const key = region ? `${region}_${variable}` : variable;
399
+ result[variable] = environment[key];
400
+ }
401
+ return result;
402
+ }
403
+ function readClientEnvironmentVariables(environment, region) {
404
+ return readEnvironmentVariables(["QSTASH_URL", "QSTASH_TOKEN"], environment, region);
405
+ }
406
+ function readReceiverEnvironmentVariables(environment, region) {
407
+ return readEnvironmentVariables(
408
+ ["QSTASH_CURRENT_SIGNING_KEY", "QSTASH_NEXT_SIGNING_KEY"],
409
+ environment,
410
+ region
411
+ );
412
+ }
413
+ function normalizeRegionHeader(region) {
414
+ if (!region) {
415
+ return void 0;
416
+ }
417
+ region = region.replaceAll("-", "_").toUpperCase();
418
+ if (VALID_REGIONS.includes(region)) {
419
+ return region;
420
+ }
421
+ console.warn(
422
+ `[Upstash QStash] Invalid UPSTASH_REGION header value: "${region}". Expected one of: ${VALID_REGIONS.join(
423
+ ", "
424
+ )}.`
425
+ );
426
+ return void 0;
427
+ }
428
+
429
+ // src/client/multi-region/incoming.ts
430
+ var getReceiverSigningKeys = ({
431
+ environment,
432
+ regionFromHeader,
433
+ config
434
+ }) => {
435
+ if (config?.currentSigningKey && config.nextSigningKey) {
436
+ return {
437
+ currentSigningKey: config.currentSigningKey,
438
+ nextSigningKey: config.nextSigningKey
439
+ };
440
+ }
441
+ const regionEnvironment = getRegionFromEnvironment(environment);
442
+ if (regionEnvironment) {
443
+ const regionHeader = normalizeRegionHeader(regionFromHeader);
444
+ if (regionHeader) {
445
+ const regionCreds = readReceiverEnvironmentVariables(environment, regionHeader);
446
+ if (regionCreds.QSTASH_CURRENT_SIGNING_KEY && regionCreds.QSTASH_NEXT_SIGNING_KEY) {
447
+ return {
448
+ currentSigningKey: regionCreds.QSTASH_CURRENT_SIGNING_KEY,
449
+ nextSigningKey: regionCreds.QSTASH_NEXT_SIGNING_KEY,
450
+ region: regionHeader
451
+ };
452
+ } else {
453
+ console.warn(
454
+ `[Upstash QStash] Signing keys not found for region "${regionHeader}". Falling back to default signing keys.`
455
+ );
456
+ }
457
+ } else {
458
+ console.warn(
459
+ `[Upstash QStash] Invalid UPSTASH_REGION header value: "${regionFromHeader}". Expected one of: EU-CENTRAL-1, US-EAST-1. Falling back to default signing keys.`
460
+ );
461
+ }
462
+ }
463
+ const defaultCreds = readReceiverEnvironmentVariables(environment);
464
+ if (defaultCreds.QSTASH_CURRENT_SIGNING_KEY && defaultCreds.QSTASH_NEXT_SIGNING_KEY) {
465
+ return {
466
+ currentSigningKey: defaultCreds.QSTASH_CURRENT_SIGNING_KEY,
467
+ nextSigningKey: defaultCreds.QSTASH_NEXT_SIGNING_KEY
468
+ };
469
+ }
470
+ };
471
+
472
+ // src/client/multi-region/outgoing.ts
473
+ var getClientCredentials = (clientCredentialConfig) => {
474
+ const credentials = resolveCredentials(clientCredentialConfig);
475
+ return verifyCredentials(credentials);
476
+ };
477
+ var resolveCredentials = ({
478
+ environment,
479
+ config
480
+ }) => {
481
+ if (config?.baseUrl && config.token) {
482
+ return {
483
+ baseUrl: config.baseUrl,
484
+ token: config.token
485
+ };
486
+ }
487
+ const region = getRegionFromEnvironment(environment);
488
+ if (region) {
489
+ const regionCreds = readClientEnvironmentVariables(environment, region);
490
+ if (regionCreds.QSTASH_URL && regionCreds.QSTASH_TOKEN) {
491
+ return {
492
+ baseUrl: regionCreds.QSTASH_URL,
493
+ token: regionCreds.QSTASH_TOKEN,
494
+ region
495
+ };
496
+ } else {
497
+ console.warn(
498
+ `[Upstash QStash] QSTASH_REGION is set to "${region}" but credentials are missing. Expected ${region}_QSTASH_URL and ${region}_QSTASH_TOKEN. Falling back to default credentials.`
499
+ );
500
+ }
501
+ }
502
+ const defaultCreds = readClientEnvironmentVariables(environment);
503
+ return {
504
+ baseUrl: config?.baseUrl ?? defaultCreds.QSTASH_URL ?? DEFAULT_QSTASH_URL,
505
+ token: config?.token ?? defaultCreds.QSTASH_TOKEN ?? ""
506
+ };
507
+ };
508
+ var verifyCredentials = (credentials) => {
509
+ const token = credentials.token;
510
+ let baseUrl = credentials.baseUrl;
511
+ baseUrl = baseUrl.replace(/\/$/, "");
512
+ if (baseUrl === "https://qstash.upstash.io/v2/publish") {
513
+ baseUrl = DEFAULT_QSTASH_URL;
514
+ }
515
+ if (!token) {
516
+ console.warn(
517
+ "[Upstash QStash] client token is not set. Either pass a token or set QSTASH_TOKEN env variable."
518
+ );
519
+ }
520
+ return { baseUrl, token };
521
+ };
522
+
523
+ // src/receiver.ts
4
524
  var SignatureError = class extends Error {
5
525
  constructor(message) {
6
526
  super(message);
@@ -11,8 +531,8 @@ var Receiver = class {
11
531
  currentSigningKey;
12
532
  nextSigningKey;
13
533
  constructor(config) {
14
- this.currentSigningKey = config.currentSigningKey;
15
- this.nextSigningKey = config.nextSigningKey;
534
+ this.currentSigningKey = config?.currentSigningKey;
535
+ this.nextSigningKey = config?.nextSigningKey;
16
536
  }
17
537
  /**
18
538
  * Verify the signature of a request.
@@ -24,11 +544,25 @@ var Receiver = class {
24
544
  * If that fails, the signature is invalid and a `SignatureError` is thrown.
25
545
  */
26
546
  async verify(request) {
547
+ const environment = getSafeEnvironment();
548
+ const signingKeys = getReceiverSigningKeys({
549
+ environment,
550
+ regionFromHeader: request.upstashRegion,
551
+ config: {
552
+ currentSigningKey: this.currentSigningKey,
553
+ nextSigningKey: this.nextSigningKey
554
+ }
555
+ });
556
+ if (!signingKeys) {
557
+ throw new Error(
558
+ "[Upstash QStash] No signing keys available for verification. See the warning above for more details."
559
+ );
560
+ }
27
561
  let payload;
28
562
  try {
29
- payload = await this.verifyWithKey(this.currentSigningKey, request);
563
+ payload = await this.verifyWithKey(signingKeys.currentSigningKey, request);
30
564
  } catch {
31
- payload = await this.verifyWithKey(this.nextSigningKey, request);
565
+ payload = await this.verifyWithKey(signingKeys.nextSigningKey, request);
32
566
  }
33
567
  this.verifyBodyAndUrl(payload, request);
34
568
  return true;
@@ -103,98 +637,18 @@ var DLQ = class {
103
637
  // there is no response
104
638
  });
105
639
  }
106
- /**
107
- * Remove multiple messages from the dlq using their `dlqId`s
108
- */
109
- async deleteMany(request) {
110
- return await this.http.request({
111
- method: "DELETE",
112
- path: ["v2", "dlq"],
113
- headers: { "Content-Type": "application/json" },
114
- body: JSON.stringify({ dlqIds: request.dlqIds })
115
- });
116
- }
117
- };
118
-
119
- // src/client/error.ts
120
- var RATELIMIT_STATUS = 429;
121
- var QstashError = class extends Error {
122
- status;
123
- constructor(message, status) {
124
- super(message);
125
- this.name = "QstashError";
126
- this.status = status;
127
- }
128
- };
129
- var QstashRatelimitError = class extends QstashError {
130
- limit;
131
- remaining;
132
- reset;
133
- constructor(args) {
134
- super(`Exceeded burst rate limit. ${JSON.stringify(args)}`, RATELIMIT_STATUS);
135
- this.name = "QstashRatelimitError";
136
- this.limit = args.limit;
137
- this.remaining = args.remaining;
138
- this.reset = args.reset;
139
- }
140
- };
141
- var QstashChatRatelimitError = class extends QstashError {
142
- limitRequests;
143
- limitTokens;
144
- remainingRequests;
145
- remainingTokens;
146
- resetRequests;
147
- resetTokens;
148
- constructor(args) {
149
- super(`Exceeded chat rate limit. ${JSON.stringify(args)}`, RATELIMIT_STATUS);
150
- this.name = "QstashChatRatelimitError";
151
- this.limitRequests = args["limit-requests"];
152
- this.limitTokens = args["limit-tokens"];
153
- this.remainingRequests = args["remaining-requests"];
154
- this.remainingTokens = args["remaining-tokens"];
155
- this.resetRequests = args["reset-requests"];
156
- this.resetTokens = args["reset-tokens"];
157
- }
158
- };
159
- var QstashDailyRatelimitError = class extends QstashError {
160
- limit;
161
- remaining;
162
- reset;
163
- constructor(args) {
164
- super(`Exceeded daily rate limit. ${JSON.stringify(args)}`, RATELIMIT_STATUS);
165
- this.name = "QstashDailyRatelimitError";
166
- this.limit = args.limit;
167
- this.remaining = args.remaining;
168
- this.reset = args.reset;
169
- }
170
- };
171
- var QStashWorkflowError = class extends QstashError {
172
- constructor(message) {
173
- super(message);
174
- this.name = "QStashWorkflowError";
175
- }
176
- };
177
- var QStashWorkflowAbort = class extends Error {
178
- stepInfo;
179
- stepName;
180
- constructor(stepName, stepInfo) {
181
- super(
182
- `This is an Upstash Workflow error thrown after a step executes. It is expected to be raised. Make sure that you await for each step. Also, if you are using try/catch blocks, you should not wrap context.run/sleep/sleepUntil/call methods with try/catch. Aborting workflow after executing step '${stepName}'.`
183
- );
184
- this.name = "QStashWorkflowAbort";
185
- this.stepName = stepName;
186
- this.stepInfo = stepInfo;
640
+ /**
641
+ * Remove multiple messages from the dlq using their `dlqId`s
642
+ */
643
+ async deleteMany(request) {
644
+ return await this.http.request({
645
+ method: "DELETE",
646
+ path: ["v2", "dlq"],
647
+ headers: { "Content-Type": "application/json" },
648
+ body: JSON.stringify({ dlqIds: request.dlqIds })
649
+ });
187
650
  }
188
651
  };
189
- var formatWorkflowError = (error) => {
190
- return error instanceof Error ? {
191
- error: error.name,
192
- message: error.message
193
- } : {
194
- error: "Error",
195
- message: "An error occured while executing workflow."
196
- };
197
- };
198
652
 
199
653
  // src/client/http.ts
200
654
  var HttpClient = class {
@@ -460,396 +914,96 @@ var Chat = class _Chat {
460
914
  ...organization ? {
461
915
  "OpenAI-Organization": organization
462
916
  } : {},
463
- ...isStream ? {
464
- Connection: "keep-alive",
465
- Accept: "text/event-stream",
466
- "Cache-Control": "no-cache"
467
- } : {},
468
- ...analyticsConfig.defaultHeaders
469
- };
470
- const response = await this.http[isStream ? "requestStream" : "request"]({
471
- path: isAnalyticsEnabled ? [] : ["v1", "chat", "completions"],
472
- method: "POST",
473
- headers,
474
- body,
475
- baseUrl: analyticsConfig.baseURL
476
- });
477
- return response;
478
- };
479
- // Helper method to get the authorization token
480
- getAuthorizationToken() {
481
- const authHeader = String(this.http.authorization);
482
- const match = /Bearer (.+)/.exec(authHeader);
483
- if (!match) {
484
- throw new Error("Invalid authorization header format");
485
- }
486
- return match[1];
487
- }
488
- /**
489
- * Calls the Upstash completions api given a PromptRequest.
490
- *
491
- * Returns a ChatCompletion or a stream of ChatCompletionChunks
492
- * if stream is enabled.
493
- *
494
- * @param request PromptRequest with system and user messages.
495
- * Note that system parameter shouldn't be passed in the case of
496
- * mistralai/Mistral-7B-Instruct-v0.2 model.
497
- * @returns Chat completion or stream
498
- */
499
- prompt = async (request) => {
500
- const chatRequest = _Chat.toChatRequest(request);
501
- return this.create(chatRequest);
502
- };
503
- };
504
-
505
- // src/client/messages.ts
506
- var Messages = class {
507
- http;
508
- constructor(http) {
509
- this.http = http;
510
- }
511
- /**
512
- * Get a message
513
- */
514
- async get(messageId) {
515
- const messagePayload = await this.http.request({
516
- method: "GET",
517
- path: ["v2", "messages", messageId]
518
- });
519
- const message = {
520
- ...messagePayload,
521
- urlGroup: messagePayload.topicName,
522
- ratePerSecond: "rate" in messagePayload ? messagePayload.rate : void 0
523
- };
524
- return message;
525
- }
526
- /**
527
- * Cancel a message
528
- */
529
- async delete(messageId) {
530
- return await this.http.request({
531
- method: "DELETE",
532
- path: ["v2", "messages", messageId],
533
- parseResponseAsJson: false
534
- });
535
- }
536
- async deleteMany(messageIds) {
537
- const result = await this.http.request({
538
- method: "DELETE",
539
- path: ["v2", "messages"],
540
- headers: { "Content-Type": "application/json" },
541
- body: JSON.stringify({ messageIds })
542
- });
543
- return result.cancelled;
544
- }
545
- async deleteAll() {
546
- const result = await this.http.request({
547
- method: "DELETE",
548
- path: ["v2", "messages"]
549
- });
550
- return result.cancelled;
551
- }
552
- };
553
-
554
- // src/client/api/base.ts
555
- var BaseProvider = class {
556
- baseUrl;
557
- token;
558
- owner;
559
- constructor(baseUrl, token, owner) {
560
- this.baseUrl = baseUrl;
561
- this.token = token;
562
- this.owner = owner;
563
- }
564
- getUrl() {
565
- return `${this.baseUrl}/${this.getRoute().join("/")}`;
566
- }
567
- };
568
-
569
- // src/client/api/llm.ts
570
- var LLMProvider = class extends BaseProvider {
571
- apiKind = "llm";
572
- organization;
573
- method = "POST";
574
- constructor(baseUrl, token, owner, organization) {
575
- super(baseUrl, token, owner);
576
- this.organization = organization;
577
- }
578
- getRoute() {
579
- return this.owner === "anthropic" ? ["v1", "messages"] : ["v1", "chat", "completions"];
580
- }
581
- getHeaders(options) {
582
- if (this.owner === "upstash" && !options.analytics) {
583
- return { "content-type": "application/json" };
584
- }
585
- const header = this.owner === "anthropic" ? "x-api-key" : "authorization";
586
- const headerValue = this.owner === "anthropic" ? this.token : `Bearer ${this.token}`;
587
- const headers = {
588
- [header]: headerValue,
589
- "content-type": "application/json"
590
- };
591
- if (this.owner === "openai" && this.organization) {
592
- headers["OpenAI-Organization"] = this.organization;
593
- }
594
- if (this.owner === "anthropic") {
595
- headers["anthropic-version"] = "2023-06-01";
596
- }
597
- return headers;
598
- }
599
- /**
600
- * Checks if callback exists and adds analytics in place if it's set.
601
- *
602
- * @param request
603
- * @param options
604
- */
605
- onFinish(providerInfo, options) {
606
- if (options.analytics) {
607
- return updateWithAnalytics(providerInfo, options.analytics);
608
- }
609
- return providerInfo;
610
- }
611
- };
612
- var upstash = () => {
613
- return new LLMProvider("https://qstash.upstash.io/llm", "", "upstash");
614
- };
615
- var openai = ({
616
- token,
617
- organization
618
- }) => {
619
- return new LLMProvider("https://api.openai.com", token, "openai", organization);
620
- };
621
- var anthropic = ({ token }) => {
622
- return new LLMProvider("https://api.anthropic.com", token, "anthropic");
623
- };
624
- var custom = ({
625
- baseUrl,
626
- token
627
- }) => {
628
- const trimmedBaseUrl = baseUrl.replace(/\/(v1\/)?chat\/completions$/, "");
629
- return new LLMProvider(trimmedBaseUrl, token, "custom");
630
- };
631
-
632
- // src/client/api/utils.ts
633
- var getProviderInfo = (api, upstashToken) => {
634
- const { name, provider, ...parameters } = api;
635
- const finalProvider = provider ?? upstash();
636
- if (finalProvider.owner === "upstash" && !finalProvider.token) {
637
- finalProvider.token = upstashToken;
638
- }
639
- if (!finalProvider.baseUrl)
640
- throw new TypeError("baseUrl cannot be empty or undefined!");
641
- if (!finalProvider.token)
642
- throw new TypeError("token cannot be empty or undefined!");
643
- if (finalProvider.apiKind !== name) {
644
- throw new TypeError(
645
- `Unexpected api name. Expected '${finalProvider.apiKind}', received ${name}`
646
- );
647
- }
648
- const providerInfo = {
649
- url: finalProvider.getUrl(),
650
- baseUrl: finalProvider.baseUrl,
651
- route: finalProvider.getRoute(),
652
- appendHeaders: finalProvider.getHeaders(parameters),
653
- owner: finalProvider.owner,
654
- method: finalProvider.method
655
- };
656
- return finalProvider.onFinish(providerInfo, parameters);
657
- };
658
- var safeJoinHeaders = (headers, record) => {
659
- const joinedHeaders = new Headers(record);
660
- for (const [header, value] of headers.entries()) {
661
- joinedHeaders.set(header, value);
662
- }
663
- return joinedHeaders;
664
- };
665
- var processApi = (request, headers, upstashToken) => {
666
- if (!request.api) {
667
- request.headers = headers;
668
- return request;
669
- }
670
- const { url, appendHeaders, owner, method } = getProviderInfo(request.api, upstashToken);
671
- if (request.api.name === "llm") {
672
- const callback = request.callback;
673
- if (!callback) {
674
- throw new TypeError("Callback cannot be undefined when using LLM api.");
675
- }
676
- return {
677
- ...request,
678
- method: request.method ?? method,
679
- headers: safeJoinHeaders(headers, appendHeaders),
680
- ...owner === "upstash" && !request.api.analytics ? { api: { name: "llm" }, url: void 0, callback } : { url, api: void 0 }
681
- };
682
- } else {
683
- return {
684
- ...request,
685
- method: request.method ?? method,
686
- headers: safeJoinHeaders(headers, appendHeaders),
687
- url,
688
- api: void 0
917
+ ...isStream ? {
918
+ Connection: "keep-alive",
919
+ Accept: "text/event-stream",
920
+ "Cache-Control": "no-cache"
921
+ } : {},
922
+ ...analyticsConfig.defaultHeaders
689
923
  };
690
- }
691
- };
692
- function updateWithAnalytics(providerInfo, analytics) {
693
- switch (analytics.name) {
694
- case "helicone": {
695
- providerInfo.appendHeaders["Helicone-Auth"] = `Bearer ${analytics.token}`;
696
- if (providerInfo.owner === "upstash") {
697
- updateProviderInfo(providerInfo, "https://qstash.helicone.ai", [
698
- "llm",
699
- ...providerInfo.route
700
- ]);
701
- } else {
702
- providerInfo.appendHeaders["Helicone-Target-Url"] = providerInfo.baseUrl;
703
- updateProviderInfo(providerInfo, "https://gateway.helicone.ai", providerInfo.route);
704
- }
705
- return providerInfo;
706
- }
707
- default: {
708
- throw new Error("Unknown analytics provider");
924
+ const response = await this.http[isStream ? "requestStream" : "request"]({
925
+ path: isAnalyticsEnabled ? [] : ["v1", "chat", "completions"],
926
+ method: "POST",
927
+ headers,
928
+ body,
929
+ baseUrl: analyticsConfig.baseURL
930
+ });
931
+ return response;
932
+ };
933
+ // Helper method to get the authorization token
934
+ getAuthorizationToken() {
935
+ const authHeader = String(this.http.authorization);
936
+ const match = /Bearer (.+)/.exec(authHeader);
937
+ if (!match) {
938
+ throw new Error("Invalid authorization header format");
709
939
  }
940
+ return match[1];
710
941
  }
711
- }
712
- function updateProviderInfo(providerInfo, baseUrl, route) {
713
- providerInfo.baseUrl = baseUrl;
714
- providerInfo.route = route;
715
- providerInfo.url = `${baseUrl}/${route.join("/")}`;
716
- }
717
-
718
- // src/client/utils.ts
719
- var isIgnoredHeader = (header) => {
720
- const lowerCaseHeader = header.toLowerCase();
721
- return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
942
+ /**
943
+ * Calls the Upstash completions api given a PromptRequest.
944
+ *
945
+ * Returns a ChatCompletion or a stream of ChatCompletionChunks
946
+ * if stream is enabled.
947
+ *
948
+ * @param request PromptRequest with system and user messages.
949
+ * Note that system parameter shouldn't be passed in the case of
950
+ * mistralai/Mistral-7B-Instruct-v0.2 model.
951
+ * @returns Chat completion or stream
952
+ */
953
+ prompt = async (request) => {
954
+ const chatRequest = _Chat.toChatRequest(request);
955
+ return this.create(chatRequest);
956
+ };
722
957
  };
723
- function prefixHeaders(headers) {
724
- const keysToBePrefixed = [...headers.keys()].filter((key) => !isIgnoredHeader(key));
725
- for (const key of keysToBePrefixed) {
726
- const value = headers.get(key);
727
- if (value !== null) {
728
- headers.set(`Upstash-Forward-${key}`, value);
729
- }
730
- headers.delete(key);
731
- }
732
- return headers;
733
- }
734
- function wrapWithGlobalHeaders(headers, globalHeaders, telemetryHeaders) {
735
- if (!globalHeaders) {
736
- return headers;
737
- }
738
- const finalHeaders = new Headers(globalHeaders);
739
- headers.forEach((value, key) => {
740
- finalHeaders.set(key, value);
741
- });
742
- telemetryHeaders?.forEach((value, key) => {
743
- if (!value)
744
- return;
745
- finalHeaders.append(key, value);
746
- });
747
- return finalHeaders;
748
- }
749
- function processHeaders(request) {
750
- const headers = prefixHeaders(new Headers(request.headers));
751
- headers.set("Upstash-Method", request.method ?? "POST");
752
- if (request.delay !== void 0) {
753
- if (typeof request.delay === "string") {
754
- headers.set("Upstash-Delay", request.delay);
755
- } else {
756
- headers.set("Upstash-Delay", `${request.delay.toFixed(0)}s`);
757
- }
758
- }
759
- if (request.notBefore !== void 0) {
760
- headers.set("Upstash-Not-Before", request.notBefore.toFixed(0));
761
- }
762
- if (request.deduplicationId !== void 0) {
763
- headers.set("Upstash-Deduplication-Id", request.deduplicationId);
764
- }
765
- if (request.contentBasedDeduplication) {
766
- headers.set("Upstash-Content-Based-Deduplication", "true");
767
- }
768
- if (request.retries !== void 0) {
769
- headers.set("Upstash-Retries", request.retries.toFixed(0));
770
- }
771
- if (request.retryDelay !== void 0) {
772
- headers.set("Upstash-Retry-Delay", request.retryDelay);
773
- }
774
- if (request.callback !== void 0) {
775
- headers.set("Upstash-Callback", request.callback);
776
- }
777
- if (request.failureCallback !== void 0) {
778
- headers.set("Upstash-Failure-Callback", request.failureCallback);
779
- }
780
- if (request.timeout !== void 0) {
781
- if (typeof request.timeout === "string") {
782
- headers.set("Upstash-Timeout", request.timeout);
783
- } else {
784
- headers.set("Upstash-Timeout", `${request.timeout}s`);
785
- }
958
+
959
+ // src/client/messages.ts
960
+ var Messages = class {
961
+ http;
962
+ constructor(http) {
963
+ this.http = http;
786
964
  }
787
- if (request.flowControl?.key) {
788
- const parallelism = request.flowControl.parallelism?.toString();
789
- const rate = (request.flowControl.rate ?? request.flowControl.ratePerSecond)?.toString();
790
- const period = typeof request.flowControl.period === "number" ? `${request.flowControl.period}s` : request.flowControl.period;
791
- const controlValue = [
792
- parallelism ? `parallelism=${parallelism}` : void 0,
793
- rate ? `rate=${rate}` : void 0,
794
- period ? `period=${period}` : void 0
795
- ].filter(Boolean);
796
- if (controlValue.length === 0) {
797
- throw new QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
798
- }
799
- headers.set("Upstash-Flow-Control-Key", request.flowControl.key);
800
- headers.set("Upstash-Flow-Control-Value", controlValue.join(", "));
965
+ /**
966
+ * Get a message
967
+ */
968
+ async get(messageId) {
969
+ const messagePayload = await this.http.request({
970
+ method: "GET",
971
+ path: ["v2", "messages", messageId]
972
+ });
973
+ const message = {
974
+ ...messagePayload,
975
+ urlGroup: messagePayload.topicName,
976
+ ratePerSecond: "rate" in messagePayload ? messagePayload.rate : void 0
977
+ };
978
+ return message;
801
979
  }
802
- if (request.label !== void 0) {
803
- headers.set("Upstash-Label", request.label);
980
+ /**
981
+ * Cancel a message
982
+ */
983
+ async delete(messageId) {
984
+ return await this.http.request({
985
+ method: "DELETE",
986
+ path: ["v2", "messages", messageId],
987
+ parseResponseAsJson: false
988
+ });
804
989
  }
805
- return headers;
806
- }
807
- function getRequestPath(request) {
808
- const nonApiPath = request.url ?? request.urlGroup ?? request.topic;
809
- if (nonApiPath)
810
- return nonApiPath;
811
- if (request.api?.name === "llm")
812
- return `api/llm`;
813
- if (request.api?.name === "email") {
814
- const providerInfo = getProviderInfo(request.api, "not-needed");
815
- return providerInfo.baseUrl;
990
+ async deleteMany(messageIds) {
991
+ const result = await this.http.request({
992
+ method: "DELETE",
993
+ path: ["v2", "messages"],
994
+ headers: { "Content-Type": "application/json" },
995
+ body: JSON.stringify({ messageIds })
996
+ });
997
+ return result.cancelled;
816
998
  }
817
- throw new QstashError(`Failed to infer request path for ${JSON.stringify(request)}`);
818
- }
819
- var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
820
- var NANOID_LENGTH = 21;
821
- function nanoid() {
822
- return [...crypto.getRandomValues(new Uint8Array(NANOID_LENGTH))].map((x) => NANOID_CHARS[x % NANOID_CHARS.length]).join("");
823
- }
824
- function decodeBase64(base64) {
825
- try {
826
- const binString = atob(base64);
827
- const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
828
- return new TextDecoder().decode(intArray);
829
- } catch (error) {
830
- try {
831
- const result = atob(base64);
832
- console.warn(
833
- `Upstash QStash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
834
- );
835
- return result;
836
- } catch (error2) {
837
- console.warn(
838
- `Upstash QStash: Failed to decode base64 "${base64}" with atob. Returning it as it is. ${error2}`
839
- );
840
- return base64;
841
- }
999
+ async deleteAll() {
1000
+ const result = await this.http.request({
1001
+ method: "DELETE",
1002
+ path: ["v2", "messages"]
1003
+ });
1004
+ return result.cancelled;
842
1005
  }
843
- }
844
- function getRuntime() {
845
- if (typeof process === "object" && typeof process.versions == "object" && process.versions.bun)
846
- return `bun@${process.versions.bun}`;
847
- if (typeof EdgeRuntime === "string")
848
- return "edge-light";
849
- else if (typeof process === "object" && typeof process.version === "string")
850
- return `node@${process.version}`;
851
- return "";
852
- }
1006
+ };
853
1007
 
854
1008
  // src/client/queue.ts
855
1009
  var Queue = class {
@@ -1180,19 +1334,15 @@ var UrlGroups = class {
1180
1334
  };
1181
1335
 
1182
1336
  // version.ts
1183
- var VERSION = "v2.8.4";
1337
+ var VERSION = "v2.9.0-rc";
1184
1338
 
1185
1339
  // src/client/client.ts
1186
1340
  var Client = class {
1187
1341
  http;
1188
1342
  token;
1189
1343
  constructor(config) {
1190
- const environment = typeof process === "undefined" ? {} : process.env;
1191
- let baseUrl = (config?.baseUrl ?? environment.QSTASH_URL ?? "https://qstash.upstash.io").replace(/\/$/, "");
1192
- if (baseUrl === "https://qstash.upstash.io/v2/publish") {
1193
- baseUrl = "https://qstash.upstash.io";
1194
- }
1195
- const token = config?.token ?? environment.QSTASH_TOKEN;
1344
+ const environment = getSafeEnvironment();
1345
+ const { baseUrl, token } = getClientCredentials({ environment, config });
1196
1346
  const enableTelemetry = environment.UPSTASH_DISABLE_TELEMETRY ? false : config?.enableTelemetry ?? true;
1197
1347
  const isCloudflare = typeof caches !== "undefined" && "default" in caches;
1198
1348
  const telemetryHeaders = new Headers(
@@ -1211,11 +1361,6 @@ var Client = class {
1211
1361
  //@ts-expect-error caused by undici and bunjs type overlap
1212
1362
  telemetryHeaders
1213
1363
  });
1214
- if (!token) {
1215
- console.warn(
1216
- "[Upstash QStash] client token is not set. Either pass a token or set QSTASH_TOKEN env variable."
1217
- );
1218
- }
1219
1364
  this.token = token;
1220
1365
  }
1221
1366
  /**
@@ -2831,8 +2976,11 @@ var Workflow = class {
2831
2976
  };
2832
2977
 
2833
2978
  export {
2834
- SignatureError,
2835
- Receiver,
2979
+ BaseProvider,
2980
+ upstash,
2981
+ openai,
2982
+ anthropic,
2983
+ custom,
2836
2984
  QstashError,
2837
2985
  QstashRatelimitError,
2838
2986
  QstashChatRatelimitError,
@@ -2840,15 +2988,12 @@ export {
2840
2988
  QStashWorkflowError,
2841
2989
  QStashWorkflowAbort,
2842
2990
  formatWorkflowError,
2991
+ decodeBase64,
2992
+ SignatureError,
2993
+ Receiver,
2843
2994
  setupAnalytics,
2844
2995
  Chat,
2845
2996
  Messages,
2846
- BaseProvider,
2847
- upstash,
2848
- openai,
2849
- anthropic,
2850
- custom,
2851
- decodeBase64,
2852
2997
  Schedules,
2853
2998
  UrlGroups,
2854
2999
  StepTypes,