@upstash/qstash
Version:
Official Typescript client for QStash
1,622 lines (1,600 loc) • 101 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// platforms/h3.ts
var h3_exports = {};
__export(h3_exports, {
serve: () => serve2,
verifySignatureH3: () => verifySignatureH3
});
module.exports = __toCommonJS(h3_exports);
// node_modules/defu/dist/defu.mjs
function isPlainObject(value) {
if (value === null || typeof value !== "object") {
return false;
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) {
return false;
}
if (Symbol.iterator in value) {
return false;
}
if (Symbol.toStringTag in value) {
return Object.prototype.toString.call(value) === "[object Module]";
}
return true;
}
function _defu(baseObject, defaults, namespace = ".", merger) {
if (!isPlainObject(defaults)) {
return _defu(baseObject, {}, namespace, merger);
}
const object = Object.assign({}, defaults);
for (const key in baseObject) {
if (key === "__proto__" || key === "constructor") {
continue;
}
const value = baseObject[key];
if (value === null || value === void 0) {
continue;
}
if (merger && merger(object, key, value, namespace)) {
continue;
}
if (Array.isArray(value) && Array.isArray(object[key])) {
object[key] = [...value, ...object[key]];
} else if (isPlainObject(value) && isPlainObject(object[key])) {
object[key] = _defu(
value,
object[key],
(namespace ? `${namespace}.` : "") + key.toString(),
merger
);
} else {
object[key] = value;
}
}
return object;
}
function createDefu(merger) {
return (...arguments_) => (
// eslint-disable-next-line unicorn/no-array-reduce
arguments_.reduce((p, c) => _defu(p, c, "", merger), {})
);
}
var defu = createDefu();
var defuFn = createDefu((object, key, currentValue) => {
if (object[key] !== void 0 && typeof currentValue === "function") {
object[key] = currentValue(object[key]);
return true;
}
});
var defuArrayFn = createDefu((object, key, currentValue) => {
if (Array.isArray(object[key]) && typeof currentValue === "function") {
object[key] = currentValue(object[key]);
return true;
}
});
// node_modules/h3/dist/index.mjs
function hasProp(obj, prop) {
try {
return prop in obj;
} catch {
return false;
}
}
var __defProp$2 = Object.defineProperty;
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$2 = (obj, key, value) => {
__defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
var H3Error = class extends Error {
constructor(message, opts = {}) {
super(message, opts);
__publicField$2(this, "statusCode", 500);
__publicField$2(this, "fatal", false);
__publicField$2(this, "unhandled", false);
__publicField$2(this, "statusMessage");
__publicField$2(this, "data");
__publicField$2(this, "cause");
if (opts.cause && !this.cause) {
this.cause = opts.cause;
}
}
toJSON() {
const obj = {
message: this.message,
statusCode: sanitizeStatusCode(this.statusCode, 500)
};
if (this.statusMessage) {
obj.statusMessage = sanitizeStatusMessage(this.statusMessage);
}
if (this.data !== void 0) {
obj.data = this.data;
}
return obj;
}
};
__publicField$2(H3Error, "__h3_error__", true);
function createError(input) {
if (typeof input === "string") {
return new H3Error(input);
}
if (isError(input)) {
return input;
}
const err4 = new H3Error(input.message ?? input.statusMessage ?? "", {
cause: input.cause || input
});
if (hasProp(input, "stack")) {
try {
Object.defineProperty(err4, "stack", {
get() {
return input.stack;
}
});
} catch {
try {
err4.stack = input.stack;
} catch {
}
}
}
if (input.data) {
err4.data = input.data;
}
if (input.statusCode) {
err4.statusCode = sanitizeStatusCode(input.statusCode, err4.statusCode);
} else if (input.status) {
err4.statusCode = sanitizeStatusCode(input.status, err4.statusCode);
}
if (input.statusMessage) {
err4.statusMessage = input.statusMessage;
} else if (input.statusText) {
err4.statusMessage = input.statusText;
}
if (err4.statusMessage) {
const originalMessage = err4.statusMessage;
const sanitizedMessage = sanitizeStatusMessage(err4.statusMessage);
if (sanitizedMessage !== originalMessage) {
console.warn(
"[h3] Please prefer using `message` for longer error messages instead of `statusMessage`. In the future, `statusMessage` will be sanitized by default."
);
}
}
if (input.fatal !== void 0) {
err4.fatal = input.fatal;
}
if (input.unhandled !== void 0) {
err4.unhandled = input.unhandled;
}
return err4;
}
function isError(input) {
return input?.constructor?.__h3_error__ === true;
}
function isMethod(event, expected, allowHead) {
if (allowHead && event.method === "HEAD") {
return true;
}
if (typeof expected === "string") {
if (event.method === expected) {
return true;
}
} else if (expected.includes(event.method)) {
return true;
}
return false;
}
function assertMethod(event, expected, allowHead) {
if (!isMethod(event, expected, allowHead)) {
throw createError({
statusCode: 405,
statusMessage: "HTTP method is not allowed."
});
}
}
function getRequestHeaders(event) {
const _headers = {};
for (const key in event.node.req.headers) {
const val = event.node.req.headers[key];
_headers[key] = Array.isArray(val) ? val.filter(Boolean).join(", ") : val;
}
return _headers;
}
function getRequestHeader(event, name) {
const headers = getRequestHeaders(event);
const value = headers[name.toLowerCase()];
return value;
}
var getHeader = getRequestHeader;
var RawBodySymbol = Symbol.for("h3RawBody");
var ParsedBodySymbol = Symbol.for("h3ParsedBody");
var PayloadMethods$1 = ["PATCH", "POST", "PUT", "DELETE"];
function readRawBody(event, encoding = "utf8") {
assertMethod(event, PayloadMethods$1);
const _rawBody = event._requestBody || event.web?.request?.body || event.node.req[RawBodySymbol] || event.node.req.rawBody || event.node.req.body;
if (_rawBody) {
const promise2 = Promise.resolve(_rawBody).then((_resolved) => {
if (Buffer.isBuffer(_resolved)) {
return _resolved;
}
if (typeof _resolved.pipeTo === "function") {
return new Promise((resolve, reject) => {
const chunks = [];
_resolved.pipeTo(
new WritableStream({
write(chunk) {
chunks.push(chunk);
},
close() {
resolve(Buffer.concat(chunks));
},
abort(reason) {
reject(reason);
}
})
).catch(reject);
});
} else if (typeof _resolved.pipe === "function") {
return new Promise((resolve, reject) => {
const chunks = [];
_resolved.on("data", (chunk) => {
chunks.push(chunk);
}).on("end", () => {
resolve(Buffer.concat(chunks));
}).on("error", reject);
});
}
if (_resolved.constructor === Object) {
return Buffer.from(JSON.stringify(_resolved));
}
return Buffer.from(_resolved);
});
return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
}
if (!Number.parseInt(event.node.req.headers["content-length"] || "") && !String(event.node.req.headers["transfer-encoding"] ?? "").split(",").map((e) => e.trim()).filter(Boolean).includes("chunked")) {
return Promise.resolve(void 0);
}
const promise = event.node.req[RawBodySymbol] = new Promise(
(resolve, reject) => {
const bodyData = [];
event.node.req.on("error", (err4) => {
reject(err4);
}).on("data", (chunk) => {
bodyData.push(chunk);
}).on("end", () => {
resolve(Buffer.concat(bodyData));
});
}
);
const result = encoding ? promise.then((buff) => buff.toString(encoding)) : promise;
return result;
}
var DISALLOWED_STATUS_CHARS = /[^\u0009\u0020-\u007E]/g;
function sanitizeStatusMessage(statusMessage = "") {
return statusMessage.replace(DISALLOWED_STATUS_CHARS, "");
}
function sanitizeStatusCode(statusCode, defaultStatusCode = 200) {
if (!statusCode) {
return defaultStatusCode;
}
if (typeof statusCode === "string") {
statusCode = Number.parseInt(statusCode, 10);
}
if (statusCode < 100 || statusCode > 999) {
return defaultStatusCode;
}
return statusCode;
}
var getSessionPromise = Symbol("getSession");
function defineEventHandler(handler) {
if (typeof handler === "function") {
handler.__is_handler__ = true;
return handler;
}
const _hooks = {
onRequest: _normalizeArray(handler.onRequest),
onBeforeResponse: _normalizeArray(handler.onBeforeResponse)
};
const _handler = (event) => {
return _callHandler(event, handler.handler, _hooks);
};
_handler.__is_handler__ = true;
_handler.__resolve__ = handler.handler.__resolve__;
_handler.__websocket__ = handler.websocket;
return _handler;
}
function _normalizeArray(input) {
return input ? Array.isArray(input) ? input : [input] : void 0;
}
async function _callHandler(event, handler, hooks) {
if (hooks.onRequest) {
for (const hook of hooks.onRequest) {
await hook(event);
if (event.handled) {
return;
}
}
}
const body = await handler(event);
const response = { body };
if (hooks.onBeforeResponse) {
for (const hook of hooks.onBeforeResponse) {
await hook(event, response);
}
}
return response.body;
}
var H3Headers = globalThis.Headers;
var H3Response = globalThis.Response;
// src/receiver.ts
var jose = __toESM(require("jose"));
var import_crypto_js = __toESM(require("crypto-js"));
var SignatureError = class extends Error {
constructor(message) {
super(message);
this.name = "SignatureError";
}
};
var Receiver = class {
currentSigningKey;
nextSigningKey;
constructor(config) {
this.currentSigningKey = config.currentSigningKey;
this.nextSigningKey = config.nextSigningKey;
}
/**
* Verify the signature of a request.
*
* Tries to verify the signature with the current signing key.
* If that fails, maybe because you have rotated the keys recently, it will
* try to verify the signature with the next signing key.
*
* If that fails, the signature is invalid and a `SignatureError` is thrown.
*/
async verify(request) {
let payload;
try {
payload = await this.verifyWithKey(this.currentSigningKey, request);
} catch {
payload = await this.verifyWithKey(this.nextSigningKey, request);
}
this.verifyBodyAndUrl(payload, request);
return true;
}
/**
* Verify signature with a specific signing key
*/
async verifyWithKey(key, request) {
const jwt = await jose.jwtVerify(request.signature, new TextEncoder().encode(key), {
issuer: "Upstash",
clockTolerance: request.clockTolerance
}).catch((error) => {
throw new SignatureError(error.message);
});
return jwt.payload;
}
verifyBodyAndUrl(payload, request) {
const p = payload;
if (request.url !== void 0 && p.sub !== request.url) {
throw new SignatureError(`invalid subject: ${p.sub}, want: ${request.url}`);
}
const bodyHash = import_crypto_js.default.SHA256(request.body).toString(import_crypto_js.default.enc.Base64url);
const padding = new RegExp(/=+$/);
if (p.body.replace(padding, "") !== bodyHash.replace(padding, "")) {
throw new SignatureError(`body hash does not match, want: ${p.body}, got: ${bodyHash}`);
}
}
};
// src/client/dlq.ts
var DLQ = class {
http;
constructor(http) {
this.http = http;
}
/**
* List messages in the dlq
*/
async listMessages(options) {
const filterPayload = {
...options?.filter,
topicName: options?.filter?.urlGroup
};
const messagesPayload = await this.http.request({
method: "GET",
path: ["v2", "dlq"],
query: {
cursor: options?.cursor,
count: options?.count,
...filterPayload
}
});
return {
messages: messagesPayload.messages.map((message) => {
return {
...message,
urlGroup: message.topicName,
ratePerSecond: "rate" in message ? message.rate : void 0
};
}),
cursor: messagesPayload.cursor
};
}
/**
* Remove a message from the dlq using it's `dlqId`
*/
async delete(dlqMessageId) {
return await this.http.request({
method: "DELETE",
path: ["v2", "dlq", dlqMessageId],
parseResponseAsJson: false
// there is no response
});
}
/**
* Remove multiple messages from the dlq using their `dlqId`s
*/
async deleteMany(request) {
return await this.http.request({
method: "DELETE",
path: ["v2", "dlq"],
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ dlqIds: request.dlqIds })
});
}
};
// src/client/error.ts
var RATELIMIT_STATUS = 429;
var QstashError = class extends Error {
status;
constructor(message, status) {
super(message);
this.name = "QstashError";
this.status = status;
}
};
var QstashRatelimitError = class extends QstashError {
limit;
remaining;
reset;
constructor(args) {
super(`Exceeded burst rate limit. ${JSON.stringify(args)}`, RATELIMIT_STATUS);
this.name = "QstashRatelimitError";
this.limit = args.limit;
this.remaining = args.remaining;
this.reset = args.reset;
}
};
var QstashChatRatelimitError = class extends QstashError {
limitRequests;
limitTokens;
remainingRequests;
remainingTokens;
resetRequests;
resetTokens;
constructor(args) {
super(`Exceeded chat rate limit. ${JSON.stringify(args)}`, RATELIMIT_STATUS);
this.name = "QstashChatRatelimitError";
this.limitRequests = args["limit-requests"];
this.limitTokens = args["limit-tokens"];
this.remainingRequests = args["remaining-requests"];
this.remainingTokens = args["remaining-tokens"];
this.resetRequests = args["reset-requests"];
this.resetTokens = args["reset-tokens"];
}
};
var QstashDailyRatelimitError = class extends QstashError {
limit;
remaining;
reset;
constructor(args) {
super(`Exceeded daily rate limit. ${JSON.stringify(args)}`, RATELIMIT_STATUS);
this.name = "QstashDailyRatelimitError";
this.limit = args.limit;
this.remaining = args.remaining;
this.reset = args.reset;
}
};
var QStashWorkflowError = class extends QstashError {
constructor(message) {
super(message);
this.name = "QStashWorkflowError";
}
};
var QStashWorkflowAbort = class extends Error {
stepInfo;
stepName;
constructor(stepName, stepInfo) {
super(
`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}'.`
);
this.name = "QStashWorkflowAbort";
this.stepName = stepName;
this.stepInfo = stepInfo;
}
};
var formatWorkflowError = (error) => {
return error instanceof Error ? {
error: error.name,
message: error.message
} : {
error: "Error",
message: "An error occured while executing workflow."
};
};
// src/client/http.ts
var HttpClient = class {
baseUrl;
authorization;
options;
retry;
headers;
telemetryHeaders;
constructor(config) {
this.baseUrl = config.baseUrl.replace(/\/$/, "");
this.authorization = config.authorization;
this.retry = // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
typeof config.retry === "boolean" && !config.retry ? {
attempts: 1,
backoff: () => 0
} : {
attempts: config.retry?.retries ?? 5,
backoff: config.retry?.backoff ?? ((retryCount) => Math.exp(retryCount) * 50)
};
this.headers = config.headers;
this.telemetryHeaders = config.telemetryHeaders;
}
async request(request) {
const { response } = await this.requestWithBackoff(request);
if (request.parseResponseAsJson === false) {
return void 0;
}
return await response.json();
}
async *requestStream(request) {
const { response } = await this.requestWithBackoff(request);
if (!response.body) {
throw new Error("No response body");
}
const body = response.body;
const reader = body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
const chunkText = decoder.decode(value, { stream: true });
const chunks = chunkText.split("\n").filter(Boolean);
for (const chunk of chunks) {
if (chunk.startsWith("data: ")) {
const data = chunk.slice(6);
if (data === "[DONE]") {
break;
}
yield JSON.parse(data);
}
}
}
} finally {
await reader.cancel();
}
}
requestWithBackoff = async (request) => {
const [url, requestOptions] = this.processRequest(request);
let response = void 0;
let error = void 0;
for (let index = 0; index <= this.retry.attempts; index++) {
try {
response = await fetch(url.toString(), requestOptions);
break;
} catch (error_) {
error = error_;
if (index < this.retry.attempts) {
await new Promise((r) => setTimeout(r, this.retry.backoff(index)));
}
}
}
if (!response) {
throw error ?? new Error("Exhausted all retries");
}
await this.checkResponse(response);
return {
response,
error
};
};
processRequest = (request) => {
const headers = new Headers(request.headers);
if (!headers.has("Authorization")) {
headers.set("Authorization", this.authorization);
}
const requestOptions = {
method: request.method,
headers,
body: request.body,
keepalive: request.keepalive
};
const url = new URL([request.baseUrl ?? this.baseUrl, ...request.path].join("/"));
if (request.query) {
for (const [key, value] of Object.entries(request.query)) {
if (value !== void 0) {
url.searchParams.set(key, value.toString());
}
}
}
return [url.toString(), requestOptions];
};
async checkResponse(response) {
if (response.status === 429) {
if (response.headers.get("x-ratelimit-limit-requests")) {
throw new QstashChatRatelimitError({
"limit-requests": response.headers.get("x-ratelimit-limit-requests"),
"limit-tokens": response.headers.get("x-ratelimit-limit-tokens"),
"remaining-requests": response.headers.get("x-ratelimit-remaining-requests"),
"remaining-tokens": response.headers.get("x-ratelimit-remaining-tokens"),
"reset-requests": response.headers.get("x-ratelimit-reset-requests"),
"reset-tokens": response.headers.get("x-ratelimit-reset-tokens")
});
} else if (response.headers.get("RateLimit-Limit")) {
throw new QstashDailyRatelimitError({
limit: response.headers.get("RateLimit-Limit"),
remaining: response.headers.get("RateLimit-Remaining"),
reset: response.headers.get("RateLimit-Reset")
});
}
throw new QstashRatelimitError({
limit: response.headers.get("Burst-RateLimit-Limit"),
remaining: response.headers.get("Burst-RateLimit-Remaining"),
reset: response.headers.get("Burst-RateLimit-Reset")
});
}
if (response.status < 200 || response.status >= 300) {
const body = await response.text();
throw new QstashError(
body.length > 0 ? body : `Error: status=${response.status}`,
response.status
);
}
}
};
// src/client/llm/providers.ts
var setupAnalytics = (analytics, providerApiKey, providerBaseUrl, provider) => {
if (!analytics)
return {};
switch (analytics.name) {
case "helicone": {
switch (provider) {
case "upstash": {
return {
baseURL: "https://qstash.helicone.ai/llm/v1/chat/completions",
defaultHeaders: {
"Helicone-Auth": `Bearer ${analytics.token}`,
Authorization: `Bearer ${providerApiKey}`
}
};
}
default: {
return {
baseURL: "https://gateway.helicone.ai/v1/chat/completions",
defaultHeaders: {
"Helicone-Auth": `Bearer ${analytics.token}`,
"Helicone-Target-Url": providerBaseUrl,
Authorization: `Bearer ${providerApiKey}`
}
};
}
}
}
default: {
throw new Error("Unknown analytics provider");
}
}
};
// src/client/llm/chat.ts
var Chat = class _Chat {
http;
token;
constructor(http, token) {
this.http = http;
this.token = token;
}
static toChatRequest(request) {
const messages = [];
messages.push(
{ role: "system", content: request.system },
{ role: "user", content: request.user }
);
const chatRequest = { ...request, messages };
return chatRequest;
}
/**
* Calls the Upstash completions api given a ChatRequest.
*
* Returns a ChatCompletion or a stream of ChatCompletionChunks
* if stream is enabled.
*
* @param request ChatRequest with messages
* @returns Chat completion or stream
*/
create = async (request) => {
if (request.provider.owner != "upstash")
return this.createThirdParty(request);
const body = JSON.stringify(request);
let baseUrl = void 0;
let headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${this.token}`,
..."stream" in request && request.stream ? {
Connection: "keep-alive",
Accept: "text/event-stream",
"Cache-Control": "no-cache"
} : {}
};
if (request.analytics) {
const { baseURL, defaultHeaders } = setupAnalytics(
{ name: "helicone", token: request.analytics.token },
this.getAuthorizationToken(),
request.provider.baseUrl,
"upstash"
);
headers = { ...headers, ...defaultHeaders };
baseUrl = baseURL;
}
const path = request.analytics ? [] : ["llm", "v1", "chat", "completions"];
return "stream" in request && request.stream ? this.http.requestStream({
path,
method: "POST",
headers,
baseUrl,
body
}) : this.http.request({
path,
method: "POST",
headers,
baseUrl,
body
});
};
/**
* Calls the Upstash completions api given a ChatRequest.
*
* Returns a ChatCompletion or a stream of ChatCompletionChunks
* if stream is enabled.
*
* @param request ChatRequest with messages
* @returns Chat completion or stream
*/
createThirdParty = async (request) => {
const { baseUrl, token, owner, organization } = request.provider;
if (owner === "upstash")
throw new Error("Upstash is not 3rd party provider!");
delete request.provider;
delete request.system;
const analytics = request.analytics;
delete request.analytics;
const body = JSON.stringify(request);
const isAnalyticsEnabled = analytics?.name && analytics.token;
const analyticsConfig = analytics?.name && analytics.token ? setupAnalytics({ name: analytics.name, token: analytics.token }, token, baseUrl, owner) : { defaultHeaders: void 0, baseURL: baseUrl };
const isStream = "stream" in request && request.stream;
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
...organization ? {
"OpenAI-Organization": organization
} : {},
...isStream ? {
Connection: "keep-alive",
Accept: "text/event-stream",
"Cache-Control": "no-cache"
} : {},
...analyticsConfig.defaultHeaders
};
const response = await this.http[isStream ? "requestStream" : "request"]({
path: isAnalyticsEnabled ? [] : ["v1", "chat", "completions"],
method: "POST",
headers,
body,
baseUrl: analyticsConfig.baseURL
});
return response;
};
// Helper method to get the authorization token
getAuthorizationToken() {
const authHeader = String(this.http.authorization);
const match = /Bearer (.+)/.exec(authHeader);
if (!match) {
throw new Error("Invalid authorization header format");
}
return match[1];
}
/**
* Calls the Upstash completions api given a PromptRequest.
*
* Returns a ChatCompletion or a stream of ChatCompletionChunks
* if stream is enabled.
*
* @param request PromptRequest with system and user messages.
* Note that system parameter shouldn't be passed in the case of
* mistralai/Mistral-7B-Instruct-v0.2 model.
* @returns Chat completion or stream
*/
prompt = async (request) => {
const chatRequest = _Chat.toChatRequest(request);
return this.create(chatRequest);
};
};
// src/client/messages.ts
var Messages = class {
http;
constructor(http) {
this.http = http;
}
/**
* Get a message
*/
async get(messageId) {
const messagePayload = await this.http.request({
method: "GET",
path: ["v2", "messages", messageId]
});
const message = {
...messagePayload,
urlGroup: messagePayload.topicName,
ratePerSecond: "rate" in messagePayload ? messagePayload.rate : void 0
};
return message;
}
/**
* Cancel a message
*/
async delete(messageId) {
return await this.http.request({
method: "DELETE",
path: ["v2", "messages", messageId],
parseResponseAsJson: false
});
}
async deleteMany(messageIds) {
const result = await this.http.request({
method: "DELETE",
path: ["v2", "messages"],
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messageIds })
});
return result.cancelled;
}
async deleteAll() {
const result = await this.http.request({
method: "DELETE",
path: ["v2", "messages"]
});
return result.cancelled;
}
};
// src/client/api/base.ts
var BaseProvider = class {
baseUrl;
token;
owner;
constructor(baseUrl, token, owner) {
this.baseUrl = baseUrl;
this.token = token;
this.owner = owner;
}
getUrl() {
return `${this.baseUrl}/${this.getRoute().join("/")}`;
}
};
// src/client/api/llm.ts
var LLMProvider = class extends BaseProvider {
apiKind = "llm";
organization;
method = "POST";
constructor(baseUrl, token, owner, organization) {
super(baseUrl, token, owner);
this.organization = organization;
}
getRoute() {
return this.owner === "anthropic" ? ["v1", "messages"] : ["v1", "chat", "completions"];
}
getHeaders(options) {
if (this.owner === "upstash" && !options.analytics) {
return { "content-type": "application/json" };
}
const header = this.owner === "anthropic" ? "x-api-key" : "authorization";
const headerValue = this.owner === "anthropic" ? this.token : `Bearer ${this.token}`;
const headers = {
[header]: headerValue,
"content-type": "application/json"
};
if (this.owner === "openai" && this.organization) {
headers["OpenAI-Organization"] = this.organization;
}
if (this.owner === "anthropic") {
headers["anthropic-version"] = "2023-06-01";
}
return headers;
}
/**
* Checks if callback exists and adds analytics in place if it's set.
*
* @param request
* @param options
*/
onFinish(providerInfo, options) {
if (options.analytics) {
return updateWithAnalytics(providerInfo, options.analytics);
}
return providerInfo;
}
};
var upstash = () => {
return new LLMProvider("https://qstash.upstash.io/llm", "", "upstash");
};
// src/client/api/utils.ts
var getProviderInfo = (api, upstashToken) => {
const { name, provider, ...parameters } = api;
const finalProvider = provider ?? upstash();
if (finalProvider.owner === "upstash" && !finalProvider.token) {
finalProvider.token = upstashToken;
}
if (!finalProvider.baseUrl)
throw new TypeError("baseUrl cannot be empty or undefined!");
if (!finalProvider.token)
throw new TypeError("token cannot be empty or undefined!");
if (finalProvider.apiKind !== name) {
throw new TypeError(
`Unexpected api name. Expected '${finalProvider.apiKind}', received ${name}`
);
}
const providerInfo = {
url: finalProvider.getUrl(),
baseUrl: finalProvider.baseUrl,
route: finalProvider.getRoute(),
appendHeaders: finalProvider.getHeaders(parameters),
owner: finalProvider.owner,
method: finalProvider.method
};
return finalProvider.onFinish(providerInfo, parameters);
};
var safeJoinHeaders = (headers, record) => {
const joinedHeaders = new Headers(record);
for (const [header, value] of headers.entries()) {
joinedHeaders.set(header, value);
}
return joinedHeaders;
};
var processApi = (request, headers, upstashToken) => {
if (!request.api) {
request.headers = headers;
return request;
}
const { url, appendHeaders, owner, method } = getProviderInfo(request.api, upstashToken);
if (request.api.name === "llm") {
const callback = request.callback;
if (!callback) {
throw new TypeError("Callback cannot be undefined when using LLM api.");
}
return {
...request,
method: request.method ?? method,
headers: safeJoinHeaders(headers, appendHeaders),
...owner === "upstash" && !request.api.analytics ? { api: { name: "llm" }, url: void 0, callback } : { url, api: void 0 }
};
} else {
return {
...request,
method: request.method ?? method,
headers: safeJoinHeaders(headers, appendHeaders),
url,
api: void 0
};
}
};
function updateWithAnalytics(providerInfo, analytics) {
switch (analytics.name) {
case "helicone": {
providerInfo.appendHeaders["Helicone-Auth"] = `Bearer ${analytics.token}`;
if (providerInfo.owner === "upstash") {
updateProviderInfo(providerInfo, "https://qstash.helicone.ai", [
"llm",
...providerInfo.route
]);
} else {
providerInfo.appendHeaders["Helicone-Target-Url"] = providerInfo.baseUrl;
updateProviderInfo(providerInfo, "https://gateway.helicone.ai", providerInfo.route);
}
return providerInfo;
}
default: {
throw new Error("Unknown analytics provider");
}
}
}
function updateProviderInfo(providerInfo, baseUrl, route) {
providerInfo.baseUrl = baseUrl;
providerInfo.route = route;
providerInfo.url = `${baseUrl}/${route.join("/")}`;
}
// src/client/utils.ts
var isIgnoredHeader = (header) => {
const lowerCaseHeader = header.toLowerCase();
return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
};
function prefixHeaders(headers) {
const keysToBePrefixed = [...headers.keys()].filter((key) => !isIgnoredHeader(key));
for (const key of keysToBePrefixed) {
const value = headers.get(key);
if (value !== null) {
headers.set(`Upstash-Forward-${key}`, value);
}
headers.delete(key);
}
return headers;
}
function wrapWithGlobalHeaders(headers, globalHeaders, telemetryHeaders) {
if (!globalHeaders) {
return headers;
}
const finalHeaders = new Headers(globalHeaders);
headers.forEach((value, key) => {
finalHeaders.set(key, value);
});
telemetryHeaders?.forEach((value, key) => {
if (!value)
return;
finalHeaders.append(key, value);
});
return finalHeaders;
}
function processHeaders(request) {
const headers = prefixHeaders(new Headers(request.headers));
headers.set("Upstash-Method", request.method ?? "POST");
if (request.delay !== void 0) {
if (typeof request.delay === "string") {
headers.set("Upstash-Delay", request.delay);
} else {
headers.set("Upstash-Delay", `${request.delay.toFixed(0)}s`);
}
}
if (request.notBefore !== void 0) {
headers.set("Upstash-Not-Before", request.notBefore.toFixed(0));
}
if (request.deduplicationId !== void 0) {
headers.set("Upstash-Deduplication-Id", request.deduplicationId);
}
if (request.contentBasedDeduplication) {
headers.set("Upstash-Content-Based-Deduplication", "true");
}
if (request.retries !== void 0) {
headers.set("Upstash-Retries", request.retries.toFixed(0));
}
if (request.retryDelay !== void 0) {
headers.set("Upstash-Retry-Delay", request.retryDelay);
}
if (request.callback !== void 0) {
headers.set("Upstash-Callback", request.callback);
}
if (request.failureCallback !== void 0) {
headers.set("Upstash-Failure-Callback", request.failureCallback);
}
if (request.timeout !== void 0) {
if (typeof request.timeout === "string") {
headers.set("Upstash-Timeout", request.timeout);
} else {
headers.set("Upstash-Timeout", `${request.timeout}s`);
}
}
if (request.flowControl?.key) {
const parallelism = request.flowControl.parallelism?.toString();
const rate = (request.flowControl.rate ?? request.flowControl.ratePerSecond)?.toString();
const period = typeof request.flowControl.period === "number" ? `${request.flowControl.period}s` : request.flowControl.period;
const controlValue = [
parallelism ? `parallelism=${parallelism}` : void 0,
rate ? `rate=${rate}` : void 0,
period ? `period=${period}` : void 0
].filter(Boolean);
if (controlValue.length === 0) {
throw new QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
}
headers.set("Upstash-Flow-Control-Key", request.flowControl.key);
headers.set("Upstash-Flow-Control-Value", controlValue.join(", "));
}
return headers;
}
function getRequestPath(request) {
const nonApiPath = request.url ?? request.urlGroup ?? request.topic;
if (nonApiPath)
return nonApiPath;
if (request.api?.name === "llm")
return `api/llm`;
if (request.api?.name === "email") {
const providerInfo = getProviderInfo(request.api, "not-needed");
return providerInfo.baseUrl;
}
throw new QstashError(`Failed to infer request path for ${JSON.stringify(request)}`);
}
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
var NANOID_LENGTH = 21;
function nanoid() {
return [...crypto.getRandomValues(new Uint8Array(NANOID_LENGTH))].map((x) => NANOID_CHARS[x % NANOID_CHARS.length]).join("");
}
function decodeBase64(base64) {
try {
const binString = atob(base64);
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
return new TextDecoder().decode(intArray);
} catch (error) {
try {
const result = atob(base64);
console.warn(
`Upstash QStash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
);
return result;
} catch (error2) {
console.warn(
`Upstash QStash: Failed to decode base64 "${base64}" with atob. Returning it as it is. ${error2}`
);
return base64;
}
}
}
function getRuntime() {
if (typeof process === "object" && typeof process.versions == "object" && process.versions.bun)
return `bun@${process.versions.bun}`;
if (typeof EdgeRuntime === "string")
return "edge-light";
else if (typeof process === "object" && typeof process.version === "string")
return `node@${process.version}`;
return "";
}
// src/client/queue.ts
var Queue = class {
http;
queueName;
constructor(http, queueName) {
this.http = http;
this.queueName = queueName;
}
/**
* Create or update the queue
*/
async upsert(request) {
if (!this.queueName) {
throw new Error("Please provide a queue name to the Queue constructor");
}
const body = {
queueName: this.queueName,
parallelism: request.parallelism ?? 1,
paused: request.paused ?? false
};
await this.http.request({
method: "POST",
path: ["v2", "queues"],
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(body),
parseResponseAsJson: false
});
}
/**
* Get the queue details
*/
async get() {
if (!this.queueName) {
throw new Error("Please provide a queue name to the Queue constructor");
}
return await this.http.request({
method: "GET",
path: ["v2", "queues", this.queueName]
});
}
/**
* List queues
*/
async list() {
return await this.http.request({
method: "GET",
path: ["v2", "queues"]
});
}
/**
* Delete the queue
*/
async delete() {
if (!this.queueName) {
throw new Error("Please provide a queue name to the Queue constructor");
}
await this.http.request({
method: "DELETE",
path: ["v2", "queues", this.queueName],
parseResponseAsJson: false
});
}
/**
* Enqueue a message to a queue.
*/
async enqueue(request) {
if (!this.queueName) {
throw new Error("Please provide a queue name to the Queue constructor");
}
const headers = wrapWithGlobalHeaders(
processHeaders(request),
this.http.headers,
this.http.telemetryHeaders
);
const destination = getRequestPath(request);
const response = await this.http.request({
path: ["v2", "enqueue", this.queueName, destination],
body: request.body,
headers,
method: "POST"
});
return response;
}
/**
* Enqueue a message to a queue, serializing the body to JSON.
*/
async enqueueJSON(request) {
const headers = prefixHeaders(new Headers(request.headers));
headers.set("Content-Type", "application/json");
const upstashToken = String(this.http.authorization).split("Bearer ")[1];
const nonApiRequest = processApi(request, headers, upstashToken);
const response = await this.enqueue({
...nonApiRequest,
body: JSON.stringify(nonApiRequest.body)
});
return response;
}
/**
* Pauses the queue.
*
* A paused queue will not deliver messages until
* it is resumed.
*/
async pause() {
if (!this.queueName) {
throw new Error("Please provide a queue name to the Queue constructor");
}
await this.http.request({
method: "POST",
path: ["v2", "queues", this.queueName, "pause"],
parseResponseAsJson: false
});
}
/**
* Resumes the queue.
*/
async resume() {
if (!this.queueName) {
throw new Error("Please provide a queue name to the Queue constructor");
}
await this.http.request({
method: "POST",
path: ["v2", "queues", this.queueName, "resume"],
parseResponseAsJson: false
});
}
};
// src/client/schedules.ts
var Schedules = class {
http;
constructor(http) {
this.http = http;
}
/**
* Create a schedule
*/
async create(request) {
const headers = prefixHeaders(new Headers(request.headers));
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/json");
}
headers.set("Upstash-Cron", request.cron);
if (request.method !== void 0) {
headers.set("Upstash-Method", request.method);
}
if (request.delay !== void 0) {
if (typeof request.delay === "string") {
headers.set("Upstash-Delay", request.delay);
} else {
headers.set("Upstash-Delay", `${request.delay.toFixed(0)}s`);
}
}
if (request.retries !== void 0) {
headers.set("Upstash-Retries", request.retries.toFixed(0));
}
if (request.retryDelay !== void 0) {
headers.set("Upstash-Retry-Delay", request.retryDelay);
}
if (request.callback !== void 0) {
headers.set("Upstash-Callback", request.callback);
}
if (request.failureCallback !== void 0) {
headers.set("Upstash-Failure-Callback", request.failureCallback);
}
if (request.timeout !== void 0) {
if (typeof request.timeout === "string") {
headers.set("Upstash-Timeout", request.timeout);
} else {
headers.set("Upstash-Timeout", `${request.timeout}s`);
}
}
if (request.scheduleId !== void 0) {
headers.set("Upstash-Schedule-Id", request.scheduleId);
}
if (request.queueName !== void 0) {
headers.set("Upstash-Queue-Name", request.queueName);
}
if (request.flowControl?.key) {
const parallelism = request.flowControl.parallelism?.toString();
const rate = (request.flowControl.rate ?? request.flowControl.ratePerSecond)?.toString();
const period = typeof request.flowControl.period === "number" ? `${request.flowControl.period}s` : request.flowControl.period;
const controlValue = [
parallelism ? `parallelism=${parallelism}` : void 0,
rate ? `rate=${rate}` : void 0,
period ? `period=${period}` : void 0
].filter(Boolean);
if (controlValue.length === 0) {
throw new QstashError(
"Provide at least one of parallelism or ratePerSecond for flowControl"
);
}
headers.set("Upstash-Flow-Control-Key", request.flowControl.key);
headers.set("Upstash-Flow-Control-Value", controlValue.join(", "));
}
return await this.http.request({
method: "POST",
headers: wrapWithGlobalHeaders(headers, this.http.headers, this.http.telemetryHeaders),
path: ["v2", "schedules", request.destination],
body: request.body
});
}
/**
* Get a schedule
*/
async get(scheduleId) {
const schedule = await this.http.request({
method: "GET",
path: ["v2", "schedules", scheduleId]
});
if ("rate" in schedule)
schedule.ratePerSecond = schedule.rate;
return schedule;
}
/**
* List your schedules
*/
async list() {
const schedules = await this.http.request({
method: "GET",
path: ["v2", "schedules"]
});
for (const schedule of schedules) {
if ("rate" in schedule)
schedule.ratePerSecond = schedule.rate;
}
return schedules;
}
/**
* Delete a schedule
*/
async delete(scheduleId) {
return await this.http.request({
method: "DELETE",
path: ["v2", "schedules", scheduleId],
parseResponseAsJson: false
});
}
/**
* Pauses the schedule.
*
* A paused schedule will not deliver messages until
* it is resumed.
*/
async pause({ schedule }) {
await this.http.request({
method: "PATCH",
path: ["v2", "schedules", schedule, "pause"],
parseResponseAsJson: false
});
}
/**
* Resumes the schedule.
*/
async resume({ schedule }) {
await this.http.request({
method: "PATCH",
path: ["v2", "schedules", schedule, "resume"],
parseResponseAsJson: false
});
}
};
// src/client/url-groups.ts
var UrlGroups = class {
http;
constructor(http) {
this.http = http;
}
/**
* Create a new url group with the given name and endpoints
*/
async addEndpoints(request) {
await this.http.request({
method: "POST",
path: ["v2", "topics", request.name, "endpoints"],
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ endpoints: request.endpoints }),
parseResponseAsJson: false
});
}
/**
* Remove endpoints from a url group.
*/
async removeEndpoints(request) {
await this.http.request({
method: "DELETE",
path: ["v2", "topics", request.name, "endpoints"],
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ endpoints: request.endpoints }),
parseResponseAsJson: false
});
}
/**
* Get a list of all url groups.
*/
async list() {
return await this.http.request({
method: "GET",
path: ["v2", "topics"]
});
}
/**
* Get a single url group
*/
async get(name) {
return await this.http.request({
method: "GET",
path: ["v2", "topics", name]
});
}
/**
* Delete a url group
*/
async delete(name) {
return await this.http.request({
method: "DELETE",
path: ["v2", "topics", name],
parseResponseAsJson: false
});
}
};
// src/client/workflow/constants.ts
var WORKFLOW_ID_HEADER = "Upstash-Workflow-RunId";
var WORKFLOW_INIT_HEADER = "Upstash-Workflow-Init";
var WORKFLOW_URL_HEADER = "Upstash-Workflow-Url";
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
var WORKFLOW_PROTOCOL_VERSION = "1";
var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
var DEFAULT_CONTENT_TYPE = "application/json";
var NO_CONCURRENCY = 1;
var DEFAULT_RETRIES = 3;
// src/client/workflow/context.ts
var import_neverthrow2 = require("neverthrow");
// src/client/workflow/workflow-requests.ts
var import_neverthrow = require("neverthrow");
// src/client/workflow/types.ts
var StepTypes = ["Initial", "Run", "SleepFor", "SleepUntil", "Call"];
// src/client/workflow/workflow-requests.ts
var triggerFirstInvocation = async (workflowContext, retries, debug) => {
const headers = getHeaders(
"true",
workflowContext.workflowRunId,
workflowContext.url,
workflowContext.headers,
void 0,
workflowContext.failureUrl,
retries
);
await debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
headers,
requestPayload: workflowContext.requestPayload,
url: workflowContext.url
});
try {
await workflowContext.qstashClient.publishJSON({
headers,
method: "POST",
body: workflowContext.requestPayload,
url: workflowContext.url
});
return (0, import_neverthrow.ok)("success");
} catch (error) {
const error_ = error;
return (0, import_neverthrow.err)(error_);
}
};
var triggerRouteFunction = async ({
onCleanup,
onStep
}) => {
try {
await onStep();
await onCleanup();
return (0, import_neverthrow.ok)("workflow-finished");
} catch (error) {
const error_ = error;
return error_ instanceof QStashWorkflowAbort ? (0, import_neverthrow.ok)("step-finished") : (0, import_neverthrow.err)(error_);
}
};
var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
deletedWorkflowRunId: workflowContext.workflowRunId
});
const result = await workflowContext.qstashClient.http.request({
path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
method: "DELETE",
parseResponseAsJson: false
});
await debug?.log("SUBMIT", "SUBMIT_CLEANUP", result);
};
var recreateUserHeaders = (headers) => {
const filteredHeaders = new Headers();
const pairs = headers.entries();
for (const [header, value] of pairs) {
const headerLowerCase = header.toLowerCase();
if (!headerLowerCase.startsWith("upstash-workflow-") && !headerLowerCase.startsWith("x-vercel-") && !headerLowerCase.startsWith("x-forwarded-") && headerLowerCase !== "cf-connecting-ip") {
filteredHeaders.append(header, value);
}
}
return filteredHeaders;
};
var handleThirdPartyCallResult = async (request, requestPayload, client, workflowUrl, failureUrl, retries, debug) => {
try {
if (request.headers.get("Upstash-Workflow-Callback")) {
const callbackMessage = JSON.parse(requestPayload);
if (!(callbackMessage.status >= 200 && callbackMessage.status < 300)) {
await debug?.log("WARN", "SUBMIT_THIRD_PARTY_RESULT", {
status: callbackMessage.status,
body: atob(callbackMessage.body)
});
console.warn(
`Workflow Warning: "context.call" failed with status ${callbackMessage.status} and will retry (if there are retries remaining). Error Message:
${atob(callbackMessage.body)}`
);
return (0, import_neverthrow.ok)("call-will-retry");
}
const workflowRunId = request.headers.get(WORKFLOW_ID_HEADER);
const stepIdString = request.headers.get("Upstash-Workflow-StepId");
const stepName = request.headers.get("Upstash-Workflow-StepName");
const stepType = request.headers.get("Upstash-Workflow-StepType"