@upstash/workflow
Version:
Durable, Reliable and Performant Serverless Functions
1,617 lines (1,598 loc) • 124 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Client: () => Client4,
StepTypes: () => StepTypes,
WorkflowAbort: () => WorkflowAbort,
WorkflowContext: () => WorkflowContext,
WorkflowError: () => WorkflowError,
WorkflowLogger: () => WorkflowLogger,
WorkflowNonRetryableError: () => WorkflowNonRetryableError,
WorkflowTool: () => WorkflowTool,
serve: () => serve
});
module.exports = __toCommonJS(src_exports);
// src/client/utils.ts
var import_qstash = require("@upstash/qstash");
var makeNotifyRequest = async (requester, eventId, eventData) => {
const result = await requester.request({
path: ["v2", "notify", eventId],
method: "POST",
body: typeof eventData === "string" ? eventData : JSON.stringify(eventData)
});
return result;
};
var makeGetWaitersRequest = async (requester, eventId) => {
const result = await requester.request({
path: ["v2", "waiters", eventId],
method: "GET"
});
return result;
};
var makeCancelRequest = async (requester, workflowRunId) => {
await requester.request({
path: ["v2", "workflows", "runs", `${workflowRunId}?cancel=true`],
method: "DELETE",
parseResponseAsJson: false
});
return true;
};
var getSteps = async (requester, workflowRunId, messageId, debug) => {
try {
const steps = await requester.request({
path: ["v2", "workflows", "runs", workflowRunId],
parseResponseAsJson: true
});
if (!messageId) {
await debug?.log("INFO", "ENDPOINT_START", {
message: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
});
return { steps, workflowRunEnded: false };
} else {
const index = steps.findIndex((item) => item.messageId === messageId);
if (index === -1) {
return { steps: [], workflowRunEnded: false };
}
const filteredSteps = steps.slice(0, index + 1);
await debug?.log("INFO", "ENDPOINT_START", {
message: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
});
return { steps: filteredSteps, workflowRunEnded: false };
}
} catch (error) {
if (error instanceof import_qstash.QstashError && error.status === 404) {
await debug?.log("WARN", "ENDPOINT_START", {
message: "Couldn't fetch workflow run steps. This can happen if the workflow run succesfully ends before some callback is executed.",
error
});
return { steps: void 0, workflowRunEnded: true };
} else {
throw error;
}
}
};
// src/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_FEATURE_HEADER = "Upstash-Feature-Set";
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
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;
var VERSION = "v0.2.20";
var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
var TELEMETRY_HEADER_AGENT = "Upstash-Telemetry-Agent";
// src/error.ts
var import_qstash2 = require("@upstash/qstash");
var WorkflowError = class extends import_qstash2.QstashError {
constructor(message) {
super(message);
this.name = "WorkflowError";
}
};
var WorkflowAbort = class extends Error {
stepInfo;
stepName;
/**
* whether workflow is to be canceled on abort
*/
cancelWorkflow;
/**
*
* @param stepName name of the aborting step
* @param stepInfo step information
* @param cancelWorkflow
*/
constructor(stepName, stepInfo, cancelWorkflow = false) {
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 = "WorkflowAbort";
this.stepName = stepName;
this.stepInfo = stepInfo;
this.cancelWorkflow = cancelWorkflow;
}
};
var WorkflowNonRetryableError = class extends WorkflowAbort {
/**
* @param message error message to be displayed
*/
constructor(message) {
super("fail", void 0, false);
this.name = "WorkflowNonRetryableError";
if (message) this.message = message;
}
};
var formatWorkflowError = (error) => {
return error instanceof Error ? {
error: error.name,
message: error.message,
stack: error.stack
} : {
error: "Error",
message: `An error occured while executing workflow: '${typeof error === "string" ? error : JSON.stringify(error)}'`
};
};
// src/context/auto-executor.ts
var import_qstash5 = require("@upstash/qstash");
// src/qstash/headers.ts
var import_qstash4 = require("@upstash/qstash");
// src/utils.ts
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
var NANOID_LENGTH = 21;
function getRandomInt() {
return Math.floor(Math.random() * NANOID_CHARS.length);
}
function nanoid() {
return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
}
function getWorkflowRunId(id) {
return `wfr_${id ?? nanoid()}`;
}
function decodeBase64(base64) {
const binString = atob(base64);
try {
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
return new TextDecoder().decode(intArray);
} catch (error) {
console.warn(
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
);
return binString;
}
}
// node_modules/neverthrow/dist/index.es.js
var defaultErrorConfig = {
withStackTrace: false
};
var createNeverThrowError = (message, result, config = defaultErrorConfig) => {
const data = result.isOk() ? { type: "Ok", value: result.value } : { type: "Err", value: result.error };
const maybeStack = config.withStackTrace ? new Error().stack : void 0;
return {
data,
message,
stack: maybeStack
};
};
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function(resolve) {
resolve(value);
});
}
return new (P || (P = Promise))(function(resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
function step(result) {
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, [])).next());
});
}
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function() {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __await(v) {
return this instanceof __await ? (this.v = v, this) : new __await(v);
}
function __asyncGenerator(thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function() {
return this;
}, i;
function verb(n) {
if (g[n]) i[n] = function(v) {
return new Promise(function(a, b) {
q.push([n, v, a, b]) > 1 || resume(n, v);
});
};
}
function resume(n, v) {
try {
step(g[n](v));
} catch (e) {
settle(q[0][3], e);
}
}
function step(r) {
r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r);
}
function fulfill(value) {
resume("next", value);
}
function reject(value) {
resume("throw", value);
}
function settle(f, v) {
if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]);
}
}
function __asyncDelegator(o) {
var i, p;
return i = {}, verb("next"), verb("throw", function(e) {
throw e;
}), verb("return"), i[Symbol.iterator] = function() {
return this;
}, i;
function verb(n, f) {
i[n] = o[n] ? function(v) {
return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v;
} : f;
}
}
function __asyncValues(o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function() {
return this;
}, i);
function verb(n) {
i[n] = o[n] && function(v) {
return new Promise(function(resolve, reject) {
v = o[n](v), settle(resolve, reject, v.done, v.value);
});
};
}
function settle(resolve, reject, d, v) {
Promise.resolve(v).then(function(v2) {
resolve({ value: v2, done: d });
}, reject);
}
}
var ResultAsync = class _ResultAsync {
constructor(res) {
this._promise = res;
}
static fromSafePromise(promise) {
const newPromise = promise.then((value) => new Ok(value));
return new _ResultAsync(newPromise);
}
static fromPromise(promise, errorFn) {
const newPromise = promise.then((value) => new Ok(value)).catch((e) => new Err(errorFn(e)));
return new _ResultAsync(newPromise);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static fromThrowable(fn, errorFn) {
return (...args) => {
return new _ResultAsync((() => __awaiter(this, void 0, void 0, function* () {
try {
return new Ok(yield fn(...args));
} catch (error) {
return new Err(errorFn ? errorFn(error) : error);
}
}))());
};
}
static combine(asyncResultList) {
return combineResultAsyncList(asyncResultList);
}
static combineWithAllErrors(asyncResultList) {
return combineResultAsyncListWithAllErrors(asyncResultList);
}
map(f) {
return new _ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
if (res.isErr()) {
return new Err(res.error);
}
return new Ok(yield f(res.value));
})));
}
andThrough(f) {
return new _ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
if (res.isErr()) {
return new Err(res.error);
}
const newRes = yield f(res.value);
if (newRes.isErr()) {
return new Err(newRes.error);
}
return new Ok(res.value);
})));
}
andTee(f) {
return new _ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
if (res.isErr()) {
return new Err(res.error);
}
try {
yield f(res.value);
} catch (e) {
}
return new Ok(res.value);
})));
}
mapErr(f) {
return new _ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
if (res.isOk()) {
return new Ok(res.value);
}
return new Err(yield f(res.error));
})));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
andThen(f) {
return new _ResultAsync(this._promise.then((res) => {
if (res.isErr()) {
return new Err(res.error);
}
const newValue = f(res.value);
return newValue instanceof _ResultAsync ? newValue._promise : newValue;
}));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
orElse(f) {
return new _ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
if (res.isErr()) {
return f(res.error);
}
return new Ok(res.value);
})));
}
match(ok2, _err) {
return this._promise.then((res) => res.match(ok2, _err));
}
unwrapOr(t) {
return this._promise.then((res) => res.unwrapOr(t));
}
/**
* Emulates Rust's `?` operator in `safeTry`'s body. See also `safeTry`.
*/
safeUnwrap() {
return __asyncGenerator(this, arguments, function* safeUnwrap_1() {
return yield __await(yield __await(yield* __asyncDelegator(__asyncValues(yield __await(this._promise.then((res) => res.safeUnwrap()))))));
});
}
// Makes ResultAsync implement PromiseLike<Result>
then(successCallback, failureCallback) {
return this._promise.then(successCallback, failureCallback);
}
};
var errAsync = (err2) => new ResultAsync(Promise.resolve(new Err(err2)));
var fromPromise = ResultAsync.fromPromise;
var fromSafePromise = ResultAsync.fromSafePromise;
var fromAsyncThrowable = ResultAsync.fromThrowable;
var combineResultList = (resultList) => {
let acc = ok([]);
for (const result of resultList) {
if (result.isErr()) {
acc = err(result.error);
break;
} else {
acc.map((list) => list.push(result.value));
}
}
return acc;
};
var combineResultAsyncList = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultList);
var combineResultListWithAllErrors = (resultList) => {
let acc = ok([]);
for (const result of resultList) {
if (result.isErr() && acc.isErr()) {
acc.error.push(result.error);
} else if (result.isErr() && acc.isOk()) {
acc = err([result.error]);
} else if (result.isOk() && acc.isOk()) {
acc.value.push(result.value);
}
}
return acc;
};
var combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);
var Result;
(function(Result2) {
function fromThrowable2(fn, errorFn) {
return (...args) => {
try {
const result = fn(...args);
return ok(result);
} catch (e) {
return err(errorFn ? errorFn(e) : e);
}
};
}
Result2.fromThrowable = fromThrowable2;
function combine(resultList) {
return combineResultList(resultList);
}
Result2.combine = combine;
function combineWithAllErrors(resultList) {
return combineResultListWithAllErrors(resultList);
}
Result2.combineWithAllErrors = combineWithAllErrors;
})(Result || (Result = {}));
var ok = (value) => new Ok(value);
function err(err2) {
return new Err(err2);
}
var Ok = class {
constructor(value) {
this.value = value;
}
isOk() {
return true;
}
isErr() {
return !this.isOk();
}
map(f) {
return ok(f(this.value));
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
mapErr(_f) {
return ok(this.value);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
andThen(f) {
return f(this.value);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
andThrough(f) {
return f(this.value).map((_value) => this.value);
}
andTee(f) {
try {
f(this.value);
} catch (e) {
}
return ok(this.value);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
orElse(_f) {
return ok(this.value);
}
asyncAndThen(f) {
return f(this.value);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
asyncAndThrough(f) {
return f(this.value).map(() => this.value);
}
asyncMap(f) {
return ResultAsync.fromSafePromise(f(this.value));
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
unwrapOr(_v) {
return this.value;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
match(ok2, _err) {
return ok2(this.value);
}
safeUnwrap() {
const value = this.value;
return function* () {
return value;
}();
}
_unsafeUnwrap(_) {
return this.value;
}
_unsafeUnwrapErr(config) {
throw createNeverThrowError("Called `_unsafeUnwrapErr` on an Ok", this, config);
}
};
var Err = class {
constructor(error) {
this.error = error;
}
isOk() {
return false;
}
isErr() {
return !this.isOk();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
map(_f) {
return err(this.error);
}
mapErr(f) {
return err(f(this.error));
}
andThrough(_f) {
return err(this.error);
}
andTee(_f) {
return err(this.error);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
andThen(_f) {
return err(this.error);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
orElse(f) {
return f(this.error);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
asyncAndThen(_f) {
return errAsync(this.error);
}
asyncAndThrough(_f) {
return errAsync(this.error);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
asyncMap(_f) {
return errAsync(this.error);
}
unwrapOr(v) {
return v;
}
match(_ok, err2) {
return err2(this.error);
}
safeUnwrap() {
const error = this.error;
return function* () {
yield err(error);
throw new Error("Do not use this generator out of `safeTry`");
}();
}
_unsafeUnwrap(config) {
throw createNeverThrowError("Called `_unsafeUnwrap` on an Err", this, config);
}
_unsafeUnwrapErr(_) {
return this.error;
}
};
var fromThrowable = Result.fromThrowable;
// src/types.ts
var StepTypes = [
"Initial",
"Run",
"SleepFor",
"SleepUntil",
"Call",
"Wait",
"Notify",
"Invoke"
];
// src/workflow-requests.ts
var import_qstash3 = require("@upstash/qstash");
var triggerFirstInvocation = async (params) => {
const firstInvocationParams = Array.isArray(params) ? params : [params];
const workflowContextClient = firstInvocationParams[0].workflowContext.qstashClient;
const invocationBatch = firstInvocationParams.map(
({ workflowContext, useJSONContent, telemetry, invokeCount, delay, notBefore }) => {
const { headers } = getHeaders({
initHeaderValue: "true",
workflowConfig: {
workflowRunId: workflowContext.workflowRunId,
workflowUrl: workflowContext.url,
failureUrl: workflowContext.failureUrl,
retries: workflowContext.retries,
retryDelay: workflowContext.retryDelay,
telemetry,
flowControl: workflowContext.flowControl,
useJSONContent: useJSONContent ?? false
},
invokeCount: invokeCount ?? 0,
userHeaders: workflowContext.headers
});
if (workflowContext.headers.get("content-type")) {
headers["content-type"] = workflowContext.headers.get("content-type");
}
if (useJSONContent) {
headers["content-type"] = "application/json";
}
if (workflowContext.label) {
headers[WORKFLOW_LABEL_HEADER] = workflowContext.label;
}
const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
return {
headers,
method: "POST",
body,
url: workflowContext.url,
delay,
notBefore
};
}
);
try {
const results = await workflowContextClient.batch(invocationBatch);
const invocationStatuses = [];
for (let i = 0; i < results.length; i++) {
const result = results[i];
const invocationParams = firstInvocationParams[i];
if (result.deduplicated) {
await invocationParams.debug?.log("WARN", "SUBMIT_FIRST_INVOCATION", {
message: `Workflow run ${invocationParams.workflowContext.workflowRunId} already exists. A new one isn't created.`,
headers: invocationBatch[i].headers,
requestPayload: invocationParams.workflowContext.requestPayload,
url: invocationParams.workflowContext.url,
messageId: result.messageId
});
invocationStatuses.push("workflow-run-already-exists");
} else {
await invocationParams.debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
headers: invocationBatch[i].headers,
requestPayload: invocationParams.workflowContext.requestPayload,
url: invocationParams.workflowContext.url,
messageId: result.messageId
});
invocationStatuses.push("success");
}
}
const hasAnyDeduplicated = invocationStatuses.some(
(status) => status === "workflow-run-already-exists"
);
if (hasAnyDeduplicated) {
return ok("workflow-run-already-exists");
} else {
return ok("success");
}
} catch (error) {
const error_ = error;
return err(error_);
}
};
var triggerRouteFunction = async ({
onCleanup,
onStep,
onCancel,
debug
}) => {
try {
const result = await onStep();
await onCleanup(result);
return ok("workflow-finished");
} catch (error) {
const error_ = error;
if (error instanceof import_qstash3.QstashError && error.status === 400) {
await debug?.log("WARN", "RESPONSE_WORKFLOW", {
message: `tried to append to a cancelled workflow. exiting without publishing.`,
name: error.name,
errorMessage: error.message
});
return ok("workflow-was-finished");
} else if (!(error_ instanceof WorkflowAbort)) {
return err(error_);
} else if (error_ instanceof WorkflowNonRetryableError) {
return ok(error_);
} else if (error_.cancelWorkflow) {
await onCancel();
return ok("workflow-finished");
} else {
return ok("step-finished");
}
}
};
var triggerWorkflowDelete = async (workflowContext, result, debug, cancel = false) => {
await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
deletedWorkflowRunId: workflowContext.workflowRunId
});
await workflowContext.qstashClient.http.request({
path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
method: "DELETE",
parseResponseAsJson: false,
body: JSON.stringify(result)
});
await debug?.log(
"SUBMIT",
"SUBMIT_CLEANUP",
`workflow run ${workflowContext.workflowRunId} deleted.`
);
};
var recreateUserHeaders = (headers) => {
const filteredHeaders = new Headers();
const pairs = headers.entries();
for (const [header, value] of pairs) {
const headerLowerCase = header.toLowerCase();
const isUserHeader = !headerLowerCase.startsWith("upstash-workflow-") && // https://vercel.com/docs/edge-network/headers/request-headers#x-vercel-id
!headerLowerCase.startsWith("x-vercel-") && !headerLowerCase.startsWith("x-forwarded-") && // https://blog.cloudflare.com/preventing-request-loops-using-cdn-loop/
headerLowerCase !== "cf-connecting-ip" && headerLowerCase !== "cdn-loop" && headerLowerCase !== "cf-ew-via" && headerLowerCase !== "cf-ray" && // For Render https://render.com
headerLowerCase !== "render-proxy-ttl" || headerLowerCase === WORKFLOW_LABEL_HEADER.toLocaleLowerCase();
if (isUserHeader) {
filteredHeaders.append(header, value);
}
}
return filteredHeaders;
};
var handleThirdPartyCallResult = async ({
request,
requestPayload,
client,
workflowUrl,
failureUrl,
retries,
retryDelay,
telemetry,
flowControl,
debug
}) => {
try {
if (request.headers.get("Upstash-Workflow-Callback")) {
let callbackPayload;
if (requestPayload) {
callbackPayload = requestPayload;
} else {
const workflowRunId2 = request.headers.get("upstash-workflow-runid");
const messageId = request.headers.get("upstash-message-id");
if (!workflowRunId2)
throw new WorkflowError("workflow run id missing in context.call lazy fetch.");
if (!messageId) throw new WorkflowError("message id missing in context.call lazy fetch.");
const { steps, workflowRunEnded } = await getSteps(
client.http,
workflowRunId2,
messageId,
debug
);
if (workflowRunEnded) {
return ok("workflow-ended");
}
const failingStep = steps.find((step) => step.messageId === messageId);
if (!failingStep)
throw new WorkflowError(
"Failed to submit the context.call. " + (steps.length === 0 ? "No steps found." : `No step was found with matching messageId ${messageId} out of ${steps.length} steps.`)
);
callbackPayload = atob(failingStep.body);
}
const callbackMessage = JSON.parse(callbackPayload);
if (!(callbackMessage.status >= 200 && callbackMessage.status < 300) && callbackMessage.maxRetries && callbackMessage.retried !== callbackMessage.maxRetries) {
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 (retried ${callbackMessage.retried ?? 0} out of ${callbackMessage.maxRetries} times). Error Message:
${atob(callbackMessage.body ?? "")}`
);
return 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");
const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
const contentType = request.headers.get("Upstash-Workflow-ContentType");
const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
throw new Error(
`Missing info in callback message source header: ${JSON.stringify({
workflowRunId,
stepIdString,
stepName,
stepType,
concurrentString,
contentType
})}`
);
}
const userHeaders = recreateUserHeaders(request.headers);
const { headers: requestHeaders } = getHeaders({
initHeaderValue: "false",
workflowConfig: {
workflowRunId,
workflowUrl,
failureUrl,
retries,
retryDelay,
telemetry,
flowControl
},
userHeaders,
invokeCount: Number(invokeCount)
});
const callResponse = {
status: callbackMessage.status,
body: atob(callbackMessage.body ?? ""),
header: callbackMessage.header
};
const callResultStep = {
stepId: Number(stepIdString),
stepName,
stepType,
out: JSON.stringify(callResponse),
concurrent: Number(concurrentString)
};
await debug?.log("SUBMIT", "SUBMIT_THIRD_PARTY_RESULT", {
step: callResultStep,
headers: requestHeaders,
url: workflowUrl
});
const result = await client.publishJSON({
headers: requestHeaders,
method: "POST",
body: callResultStep,
url: workflowUrl
});
await debug?.log("SUBMIT", "SUBMIT_THIRD_PARTY_RESULT", {
messageId: result.messageId
});
return ok("is-call-return");
} else {
return ok("continue-workflow");
}
} catch (error) {
const isCallReturn = request.headers.get("Upstash-Workflow-Callback");
return err(
new WorkflowError(`Error when handling call return (isCallReturn=${isCallReturn}): ${error}`)
);
}
};
var getTelemetryHeaders = (telemetry) => {
return {
[TELEMETRY_HEADER_SDK]: telemetry.sdk,
[TELEMETRY_HEADER_FRAMEWORK]: telemetry.framework ?? "unknown",
[TELEMETRY_HEADER_RUNTIME]: telemetry.runtime ?? "unknown"
};
};
var verifyRequest = async (body, signature, verifier) => {
if (!verifier) {
return;
}
try {
if (!signature) {
throw new Error("`Upstash-Signature` header is not passed.");
}
const isValid = await verifier.verify({
body,
signature
});
if (!isValid) {
throw new Error("Signature in `Upstash-Signature` header is not valid");
}
} catch (error) {
throw new WorkflowError(
`Failed to verify that the Workflow request comes from QStash: ${error}
If signature is missing, trigger the workflow endpoint by publishing your request to QStash instead of calling it directly.
If you want to disable QStash Verification, you should clear env variables QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY`
);
}
};
// src/context/steps.ts
var BaseLazyStep = class _BaseLazyStep {
stepName;
constructor(stepName) {
if (!stepName) {
throw new WorkflowError(
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
);
}
if (typeof stepName !== "string") {
console.warn(
"Workflow Warning: A workflow step name must be a string. In a future release, this will throw an error."
);
}
this.stepName = stepName;
}
/**
* parse the out field of a step result.
*
* will be called when returning the steps to the context from auto executor
*
* @param out field of the step
* @returns parsed out field
*/
parseOut(out) {
if (out === void 0) {
if (this.allowUndefinedOut) {
return void 0;
} else {
throw new WorkflowError(
`Error while parsing output of ${this.stepType} step. Expected a string, but got: undefined`
);
}
}
if (typeof out === "object") {
if (this.stepType !== "Wait") {
console.warn(
`Error while parsing ${this.stepType} step output. Expected a string, but got object. Please reach out to Upstash Support.`
);
return out;
}
return {
...out,
eventData: _BaseLazyStep.tryParsing(out.eventData)
};
}
if (typeof out !== "string") {
throw new WorkflowError(
`Error while parsing output of ${this.stepType} step. Expected a string or undefined, but got: ${typeof out}`
);
}
return this.safeParseOut(out);
}
safeParseOut(out) {
return _BaseLazyStep.tryParsing(out);
}
static tryParsing(stepOut) {
try {
return JSON.parse(stepOut);
} catch {
return stepOut;
}
}
getBody({ step }) {
step.out = JSON.stringify(step.out);
return JSON.stringify(step);
}
getHeaders({ context, telemetry, invokeCount, step }) {
return getHeaders({
initHeaderValue: "false",
workflowConfig: {
workflowRunId: context.workflowRunId,
workflowUrl: context.url,
failureUrl: context.failureUrl,
retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
retryDelay: context.retryDelay,
useJSONContent: false,
telemetry,
flowControl: context.flowControl
},
userHeaders: context.headers,
invokeCount,
stepInfo: {
step,
lazyStep: this
}
});
}
async submitStep({ context, body, headers }) {
return await context.qstashClient.batch([
{
body,
headers,
method: "POST",
retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
retryDelay: context.retryDelay,
flowControl: context.flowControl,
url: context.url
}
]);
}
};
var LazyFunctionStep = class extends BaseLazyStep {
stepFunction;
stepType = "Run";
allowUndefinedOut = true;
constructor(stepName, stepFunction) {
super(stepName);
this.stepFunction = stepFunction;
}
getPlanStep(concurrent, targetStep) {
return {
stepId: 0,
stepName: this.stepName,
stepType: this.stepType,
concurrent,
targetStep
};
}
async getResultStep(concurrent, stepId) {
let result = this.stepFunction();
if (result instanceof Promise) {
result = await result;
}
return {
stepId,
stepName: this.stepName,
stepType: this.stepType,
out: result,
concurrent
};
}
};
var LazySleepStep = class extends BaseLazyStep {
sleep;
stepType = "SleepFor";
allowUndefinedOut = true;
constructor(stepName, sleep) {
super(stepName);
this.sleep = sleep;
}
getPlanStep(concurrent, targetStep) {
return {
stepId: 0,
stepName: this.stepName,
stepType: this.stepType,
sleepFor: this.sleep,
concurrent,
targetStep
};
}
async getResultStep(concurrent, stepId) {
return await Promise.resolve({
stepId,
stepName: this.stepName,
stepType: this.stepType,
sleepFor: this.sleep,
concurrent
});
}
async submitStep({ context, body, headers, isParallel }) {
return await context.qstashClient.batch([
{
body,
headers,
method: "POST",
url: context.url,
retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
retryDelay: context.retryDelay,
flowControl: context.flowControl,
delay: isParallel ? void 0 : this.sleep
}
]);
}
};
var LazySleepUntilStep = class extends BaseLazyStep {
sleepUntil;
stepType = "SleepUntil";
allowUndefinedOut = true;
constructor(stepName, sleepUntil) {
super(stepName);
this.sleepUntil = sleepUntil;
}
getPlanStep(concurrent, targetStep) {
return {
stepId: 0,
stepName: this.stepName,
stepType: this.stepType,
sleepUntil: this.sleepUntil,
concurrent,
targetStep
};
}
async getResultStep(concurrent, stepId) {
return await Promise.resolve({
stepId,
stepName: this.stepName,
stepType: this.stepType,
sleepUntil: this.sleepUntil,
concurrent
});
}
safeParseOut() {
return void 0;
}
async submitStep({ context, body, headers, isParallel }) {
return await context.qstashClient.batch([
{
body,
headers,
method: "POST",
url: context.url,
retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
retryDelay: context.retryDelay,
flowControl: context.flowControl,
notBefore: isParallel ? void 0 : this.sleepUntil
}
]);
}
};
var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
url;
method;
body;
headers;
retries;
retryDelay;
timeout;
flowControl;
stringifyBody;
stepType = "Call";
allowUndefinedOut = false;
constructor(stepName, url, method, body, headers, retries, retryDelay, timeout, flowControl, stringifyBody) {
super(stepName);
this.url = url;
this.method = method;
this.body = body;
this.headers = headers;
this.retries = retries;
this.retryDelay = retryDelay;
this.timeout = timeout;
this.flowControl = flowControl;
this.stringifyBody = stringifyBody;
}
getPlanStep(concurrent, targetStep) {
return {
stepId: 0,
stepName: this.stepName,
stepType: this.stepType,
concurrent,
targetStep
};
}
async getResultStep(concurrent, stepId) {
return await Promise.resolve({
stepId,
stepName: this.stepName,
stepType: this.stepType,
concurrent,
callUrl: this.url,
callMethod: this.method,
callBody: this.body,
callHeaders: this.headers
});
}
safeParseOut(out) {
const { header, status, body } = JSON.parse(out);
const responseHeaders = new Headers(header);
if (_LazyCallStep.isText(responseHeaders.get("content-type"))) {
const bytes = new Uint8Array(out.length);
for (let i = 0; i < out.length; i++) {
bytes[i] = out.charCodeAt(i);
}
const processedResult = new TextDecoder().decode(bytes);
const newBody = JSON.parse(processedResult).body;
return {
status,
header,
body: BaseLazyStep.tryParsing(newBody)
};
} else {
return { header, status, body };
}
}
static applicationContentTypes = [
"application/json",
"application/xml",
"application/javascript",
"application/x-www-form-urlencoded",
"application/xhtml+xml",
"application/ld+json",
"application/rss+xml",
"application/atom+xml"
];
static isText = (contentTypeHeader) => {
if (!contentTypeHeader) {
return false;
}
if (_LazyCallStep.applicationContentTypes.some((type) => contentTypeHeader.includes(type))) {
return true;
}
if (contentTypeHeader.startsWith("text/")) {
return true;
}
return false;
};
getBody({ step }) {
if (!step.callUrl) {
throw new WorkflowError("Incompatible step received in LazyCallStep.getBody");
}
return JSON.stringify(step.callBody);
}
getHeaders({ context, telemetry, invokeCount, step }) {
const { headers, contentType } = super.getHeaders({ context, telemetry, invokeCount, step });
headers["Upstash-Retries"] = this.retries.toString();
if (this.retryDelay) {
headers["Upstash-Retry-Delay"] = this.retryDelay;
}
headers[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete,InitialBody";
if (this.flowControl) {
const { flowControlKey, flowControlValue } = prepareFlowControl(this.flowControl);
headers["Upstash-Flow-Control-Key"] = flowControlKey;
headers["Upstash-Flow-Control-Value"] = flowControlValue;
}
if (this.timeout) {
headers["Upstash-Timeout"] = this.timeout.toString();
}
const forwardedHeaders = Object.fromEntries(
Object.entries(this.headers).map(([header, value]) => [`Upstash-Forward-${header}`, value])
);
return {
headers: {
...headers,
...forwardedHeaders,
"Upstash-Callback": context.url,
"Upstash-Callback-Workflow-RunId": context.workflowRunId,
"Upstash-Callback-Workflow-CallType": "fromCallback",
"Upstash-Callback-Workflow-Init": "false",
"Upstash-Callback-Workflow-Url": context.url,
"Upstash-Callback-Feature-Set": "LazyFetch,InitialBody,WF_DetectTrigger",
"Upstash-Callback-Forward-Upstash-Workflow-Callback": "true",
"Upstash-Callback-Forward-Upstash-Workflow-StepId": step.stepId.toString(),
"Upstash-Callback-Forward-Upstash-Workflow-StepName": this.stepName,
"Upstash-Callback-Forward-Upstash-Workflow-StepType": this.stepType,
"Upstash-Callback-Forward-Upstash-Workflow-Concurrent": step.concurrent.toString(),
"Upstash-Callback-Forward-Upstash-Workflow-ContentType": contentType,
"Upstash-Workflow-CallType": "toCallback"
},
contentType
};
}
async submitStep({ context, headers }) {
let callBody;
if (this.stringifyBody) {
callBody = JSON.stringify(this.body);
} else {
if (typeof this.body === "string") {
callBody = this.body;
} else {
throw new WorkflowError(
"When stringifyBody is false, body must be a string. Please check the body type of your call step."
);
}
}
return await context.qstashClient.batch([
{
headers,
body: callBody,
method: this.method,
url: this.url,
retries: DEFAULT_RETRIES === this.retries ? void 0 : this.retries,
retryDelay: this.retryDelay,
flowControl: this.flowControl
}
]);
}
};
var LazyWaitForEventStep = class extends BaseLazyStep {
eventId;
timeout;
stepType = "Wait";
allowUndefinedOut = false;
constructor(stepName, eventId, timeout) {
super(stepName);
this.eventId = eventId;
this.timeout = timeout;
}
getPlanStep(concurrent, targetStep) {
return {
stepId: 0,
stepName: this.stepName,
stepType: this.stepType,
waitEventId: this.eventId,
timeout: this.timeout,
concurrent,
targetStep
};
}
async getResultStep(concurrent, stepId) {
return await Promise.resolve({
stepId,
stepName: this.stepName,
stepType: this.stepType,
waitEventId: this.eventId,
timeout: this.timeout,
concurrent
});
}
safeParseOut(out) {
const result = JSON.parse(out);
return {
...result,
eventData: BaseLazyStep.tryParsing(result.eventData)
};
}
getHeaders({ context, telemetry, invokeCount, step }) {
const headers = super.getHeaders({ context, telemetry, invokeCount, step });
headers.headers["Upstash-Workflow-CallType"] = "step";
return headers;
}
getBody({ context, step, headers, telemetry }) {
if (!step.waitEventId) {
throw new WorkflowError("Incompatible step received in LazyWaitForEventStep.getBody");
}
const timeoutHeaders = {
// to include user headers:
...Object.fromEntries(Object.entries(headers).map(([header, value]) => [header, [value]])),
// to include telemetry headers:
...telemetry ? Object.fromEntries(
Object.entries(getTelemetryHeaders(telemetry)).map(([header, value]) => [
header,
[value]
])
) : {},
// note: using WORKFLOW_ID_HEADER doesn't work, because Runid -> RunId:
"Upstash-Workflow-Runid": [context.workflowRunId],
[WORKFLOW_INIT_HEADER]: ["false"],
[WORKFLOW_URL_HEADER]: [context.url],
"Upstash-Workflow-CallType": ["step"]
};
const waitBody = {
url: context.url,
timeout: step.timeout,
timeoutBody: void 0,
timeoutUrl: context.url,
timeoutHeaders,
step: {
stepId: step.stepId,
stepType: "Wait",
stepName: step.stepName,
concurrent: step.concurrent,
targetStep: step.targetStep
}
};
return JSON.stringify(waitBody);
}
async submitStep({ context, body, headers }) {
const result = await context.qstashClient.http.request({
path: ["v2", "wait", this.eventId],
body,
headers,
method: "POST",
parseResponseAsJson: false
});
return [result];
}
};
var LazyNotifyStep = class extends LazyFunctionStep {
stepType = "Notify";
constructor(stepName, eventId, eventData, requester) {
super(stepName, async () => {
const notifyResponse = await makeNotifyRequest(requester, eventId, eventData);
return {
eventId,
eventData,
notifyResponse
};
});
}
safeParseOut(out) {
const result = JSON.parse(out);
return {
...result,
eventData: BaseLazyStep.tryParsing(result.eventData)
};
}
};
var LazyInvokeStep = class extends BaseLazyStep {
stepType = "Invoke";
params;
allowUndefinedOut = false;
/**
* workflow id of the invoked workflow
*/
workflowId;
constructor(stepName, {
workflow,
body,
headers = {},
workflowRunId,
retries,
retryDelay,
flowControl,
stringifyBody = true
}) {
super(stepName);
this.params = {
workflow,
body,
headers,
workflowRunId: getWorkflowRunId(workflowRunId),
retries,
retryDelay,
flowControl,
stringifyBody
};
const { workflowId } = workflow;
if (!workflowId) {
throw new WorkflowError("You can only invoke workflow which has a workflowId");
}
this.workflowId = workflowId;
}
getPlanStep(concurrent, targetStep) {
return {
stepId: 0,
stepName: this.stepName,
stepType: this.stepType,
concurrent,
targetStep
};
}
/**
* won't be used as it's the server who will add the result step
* in Invoke step.
*/
getResultStep(concurrent, stepId) {
return Promise.resolve({
stepId,
stepName: this.stepName,
stepType: this.stepType,
concurrent
});
}
safeParseOut(out) {
const result = JSON.parse(out);
return {
...result,
body: BaseLazyStep.tryParsing(result.body)
};
}
getBody({ context, step, telemetry, invokeCount }) {
const { headers: invokerHeaders } = getHeaders({
initHeaderValue: "false",
workflowConfig: {
workflowRunId: context.workflowRunId,
workflowUrl: context.url,
failureUrl: context.failureUrl,
retries: context.retries,
retryDelay: context.retryDelay,
telemetry,
flowControl: context.flowControl,
useJSONContent: false
},
userHeaders: context.headers,
invokeCount
});
invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
let invokeBody;
if (this.params.stringifyBody) {
invokeBody = JSON.stringify(this.params.body);
} else {
if (typeof this.params.body === "string") {
invokeBody = this.params.body;
} else {
throw new WorkflowError(
"When stringifyBody is false, body must be a string. Please check the body type of your invoke step."
);
}
}
const request = {
body: invokeBody,
headers: Object.fromEntries(
Object.entries(invokerHeaders).map((pairs) => [pairs[0], [pairs[1]]])
),
workflowRunId: context.workflowRunId,
workflowUrl: context.url,
step
};
return JSON.stringify(request);
}
getHeaders({ context, telemetry, invokeCount }) {
const {
workflow,
headers = {},
workflowRunId = getWorkflowRunId(),
retries,
retryDelay,
flowControl
} = this.params;
const newUrl = context.url.replace(/[^/]+$/, this.workflowId);
const {
retries: workflowRetries,
retryDelay: workflowRetryDelay,
failureFunction,
failureUrl,
useJSONContent,
flowControl: workflowFlowControl
} = workflow.options;
const { headers: triggerHeaders, contentType } = getHeaders({
initHeaderValue: "true",
workflowConfig: {
workflowRunId,
workflowUrl: newUrl,
retries: retries ?? workflowRetries,
retryDelay: retryDelay ?? workflowRetryDelay,
telemetry,
failureUrl: failureFunction ? newUrl : failureUrl,
flowControl: flowControl ?? workflowFlowControl,
useJSONContent: useJSONContent ?? false
},
invokeCount: invokeCount + 1,
userHeaders: new Headers(headers)
});
triggerHeaders["Upstash-Workflow-Invoke"] = "true";
return { headers: triggerHeaders, contentType };
}
async submitStep({ context, body, headers }) {
const newUrl = context.url.replace(/[^/]+$/, this.workflowId);
const result = await context.qstashClient.publish({
headers,
method: "POST",
body,
url: newUrl
});
return [result];
}
};
// src/agents/constants.ts
var AGENT_NAME_HEADER = "upstash-agent-name";
var MANAGER_AGENT_PROMPT = `You are an agent orchestrating other AI Agents.
These other agents have tools available to them.
Given a prompt, utilize these agents to address requests.
Don't always call all the agents provided to you at the same time. You can call one and use it's response to call another.
Avoid calling the same agent twice in one turn. Instead, prefer to call it once but provide everything
you need from that agent.
`;
// src/qstash/headers.ts
var WorkflowHeaders = class {
userHeaders;
workflowConfig;
invokeCount;
initHeaderValue;
stepInfo;
headers;
constructor({
userHeaders,
workflowConfig,
invokeCount,
initHeaderValue,
stepInfo
}) {
this.userHeaders = userHeaders;
this.workflowConfig = workflowConfig;
this.invokeCount = invokeCount;
this.initHeaderValue = initHeaderValue;
this.stepInfo = stepInfo;
this.headers = {
rawHeaders: {},
workflowHeaders: {},
failureHeaders: {}
};
}
getHeaders() {
this.addBaseHeaders();
this.addRetries();
this.addRetryDelay();
this.addFlowControl();
this.addUserHeaders();
this.addInvokeCount();
this.addFailureUrl();
const contentType = this.addContentType();
return this.prefixHeaders(contentType);
}
addBaseHeaders() {
this.headers.rawHeaders = {
...this.headers.rawHeaders,
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
[WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody,WF_Dete