@upstash/workflow 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/express.js CHANGED
@@ -15316,9 +15316,19 @@ var require_object_inspect = __commonJS({
15316
15316
  var utilInspect = require_util_inspect();
15317
15317
  var inspectCustom = utilInspect.custom;
15318
15318
  var inspectSymbol = isSymbol(inspectCustom) ? inspectCustom : null;
15319
+ var quotes = {
15320
+ __proto__: null,
15321
+ "double": '"',
15322
+ single: "'"
15323
+ };
15324
+ var quoteREs = {
15325
+ __proto__: null,
15326
+ "double": /(["\\])/g,
15327
+ single: /(['\\])/g
15328
+ };
15319
15329
  module2.exports = function inspect_(obj, options, depth, seen) {
15320
15330
  var opts = options || {};
15321
- if (has(opts, "quoteStyle") && (opts.quoteStyle !== "single" && opts.quoteStyle !== "double")) {
15331
+ if (has(opts, "quoteStyle") && !has(quotes, opts.quoteStyle)) {
15322
15332
  throw new TypeError('option "quoteStyle" must be "single" or "double"');
15323
15333
  }
15324
15334
  if (has(opts, "maxStringLength") && (typeof opts.maxStringLength === "number" ? opts.maxStringLength < 0 && opts.maxStringLength !== Infinity : opts.maxStringLength !== null)) {
@@ -15499,7 +15509,8 @@ var require_object_inspect = __commonJS({
15499
15509
  return String(obj);
15500
15510
  };
15501
15511
  function wrapQuotes(s, defaultStyle, opts) {
15502
- var quoteChar = (opts.quoteStyle || defaultStyle) === "double" ? '"' : "'";
15512
+ var style = opts.quoteStyle || defaultStyle;
15513
+ var quoteChar = quotes[style];
15503
15514
  return quoteChar + s + quoteChar;
15504
15515
  }
15505
15516
  function quote(s) {
@@ -15674,7 +15685,9 @@ var require_object_inspect = __commonJS({
15674
15685
  var trailer = "... " + remaining + " more character" + (remaining > 1 ? "s" : "");
15675
15686
  return inspectString($slice.call(str, 0, opts.maxStringLength), opts) + trailer;
15676
15687
  }
15677
- var s = $replace.call($replace.call(str, /(['\\])/g, "\\$1"), /[\x00-\x1f]/g, lowbyte);
15688
+ var quoteRE = quoteREs[opts.quoteStyle || "single"];
15689
+ quoteRE.lastIndex = 0;
15690
+ var s = $replace.call($replace.call(str, quoteRE, "\\$1"), /[\x00-\x1f]/g, lowbyte);
15678
15691
  return wrapQuotes(s, "single", opts);
15679
15692
  }
15680
15693
  function lowbyte(c) {
@@ -19657,14 +19670,14 @@ var require_etag = __commonJS({
19657
19670
  "node_modules/etag/index.js"(exports2, module2) {
19658
19671
  "use strict";
19659
19672
  module2.exports = etag;
19660
- var crypto2 = require("crypto");
19673
+ var crypto = require("crypto");
19661
19674
  var Stats = require("fs").Stats;
19662
19675
  var toString = Object.prototype.toString;
19663
19676
  function entitytag(entity) {
19664
19677
  if (entity.length === 0) {
19665
19678
  return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
19666
19679
  }
19667
- var hash = crypto2.createHash("sha1").update(entity, "utf8").digest("base64").substring(0, 27);
19680
+ var hash = crypto.createHash("sha1").update(entity, "utf8").digest("base64").substring(0, 27);
19668
19681
  var len = typeof entity === "string" ? Buffer.byteLength(entity, "utf8") : entity.length;
19669
19682
  return '"' + len.toString(16) + "-" + hash + '"';
19670
19683
  }
@@ -22560,11 +22573,11 @@ var require_request = __commonJS({
22560
22573
  var require_cookie_signature = __commonJS({
22561
22574
  "node_modules/cookie-signature/index.js"(exports2) {
22562
22575
  "use strict";
22563
- var crypto2 = require("crypto");
22576
+ var crypto = require("crypto");
22564
22577
  exports2.sign = function(val, secret) {
22565
22578
  if ("string" != typeof val) throw new TypeError("Cookie value must be provided as a string.");
22566
22579
  if ("string" != typeof secret) throw new TypeError("Secret string must be provided.");
22567
- return val + "." + crypto2.createHmac("sha256", secret).update(val).digest("base64").replace(/\=+$/, "");
22580
+ return val + "." + crypto.createHmac("sha256", secret).update(val).digest("base64").replace(/\=+$/, "");
22568
22581
  };
22569
22582
  exports2.unsign = function(val, secret) {
22570
22583
  if ("string" != typeof val) throw new TypeError("Signed cookie string must be provided.");
@@ -22573,7 +22586,7 @@ var require_cookie_signature = __commonJS({
22573
22586
  return sha1(mac) == sha1(val) ? str : false;
22574
22587
  };
22575
22588
  function sha1(str) {
22576
- return crypto2.createHash("sha1").update(str).digest("hex");
22589
+ return crypto.createHash("sha1").update(str).digest("hex");
22577
22590
  }
22578
22591
  }
22579
22592
  });
@@ -23549,22 +23562,33 @@ module.exports = __toCommonJS(express_exports);
23549
23562
 
23550
23563
  // src/error.ts
23551
23564
  var import_qstash = require("@upstash/qstash");
23552
- var QStashWorkflowError = class extends import_qstash.QstashError {
23565
+ var WorkflowError = class extends import_qstash.QstashError {
23553
23566
  constructor(message) {
23554
23567
  super(message);
23555
- this.name = "QStashWorkflowError";
23568
+ this.name = "WorkflowError";
23556
23569
  }
23557
23570
  };
23558
- var QStashWorkflowAbort = class extends Error {
23571
+ var WorkflowAbort = class extends Error {
23559
23572
  stepInfo;
23560
23573
  stepName;
23561
- constructor(stepName, stepInfo) {
23574
+ /**
23575
+ * whether workflow is to be canceled on abort
23576
+ */
23577
+ cancelWorkflow;
23578
+ /**
23579
+ *
23580
+ * @param stepName name of the aborting step
23581
+ * @param stepInfo step information
23582
+ * @param cancelWorkflow
23583
+ */
23584
+ constructor(stepName, stepInfo, cancelWorkflow = false) {
23562
23585
  super(
23563
23586
  `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}'.`
23564
23587
  );
23565
- this.name = "QStashWorkflowAbort";
23588
+ this.name = "WorkflowAbort";
23566
23589
  this.stepName = stepName;
23567
23590
  this.stepInfo = stepInfo;
23591
+ this.cancelWorkflow = cancelWorkflow;
23568
23592
  }
23569
23593
  };
23570
23594
  var formatWorkflowError = (error) => {
@@ -23586,6 +23610,44 @@ var makeNotifyRequest = async (requester, eventId, eventData) => {
23586
23610
  });
23587
23611
  return result;
23588
23612
  };
23613
+ var makeCancelRequest = async (requester, workflowRunId) => {
23614
+ await requester.request({
23615
+ path: ["v2", "workflows", "runs", `${workflowRunId}?cancel=true`],
23616
+ method: "DELETE",
23617
+ parseResponseAsJson: false
23618
+ });
23619
+ return true;
23620
+ };
23621
+ var getSteps = async (requester, workflowRunId, messageId, debug) => {
23622
+ try {
23623
+ const steps = await requester.request({
23624
+ path: ["v2", "workflows", "runs", workflowRunId],
23625
+ parseResponseAsJson: true
23626
+ });
23627
+ if (!messageId) {
23628
+ await debug?.log("INFO", "ENDPOINT_START", {
23629
+ message: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
23630
+ });
23631
+ return steps;
23632
+ } else {
23633
+ const index = steps.findIndex((item) => item.messageId === messageId);
23634
+ if (index === -1) {
23635
+ return [];
23636
+ }
23637
+ const filteredSteps = steps.slice(0, index + 1);
23638
+ await debug?.log("INFO", "ENDPOINT_START", {
23639
+ message: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
23640
+ });
23641
+ return filteredSteps;
23642
+ }
23643
+ } catch (error) {
23644
+ await debug?.log("ERROR", "ERROR", {
23645
+ message: "failed while fetching steps.",
23646
+ error
23647
+ });
23648
+ throw new WorkflowError(`Failed while pulling steps. ${error}`);
23649
+ }
23650
+ };
23589
23651
 
23590
23652
  // src/context/steps.ts
23591
23653
  var BaseLazyStep = class {
@@ -24200,6 +24262,7 @@ var StepTypes = [
24200
24262
  ];
24201
24263
 
24202
24264
  // src/workflow-requests.ts
24265
+ var import_qstash2 = require("@upstash/qstash");
24203
24266
  var triggerFirstInvocation = async (workflowContext, retries, debug) => {
24204
24267
  const { headers } = getHeaders(
24205
24268
  "true",
@@ -24210,20 +24273,32 @@ var triggerFirstInvocation = async (workflowContext, retries, debug) => {
24210
24273
  workflowContext.failureUrl,
24211
24274
  retries
24212
24275
  );
24213
- await debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
24214
- headers,
24215
- requestPayload: workflowContext.requestPayload,
24216
- url: workflowContext.url
24217
- });
24218
24276
  try {
24219
24277
  const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
24220
- await workflowContext.qstashClient.publish({
24278
+ const result = await workflowContext.qstashClient.publish({
24221
24279
  headers,
24222
24280
  method: "POST",
24223
24281
  body,
24224
24282
  url: workflowContext.url
24225
24283
  });
24226
- return ok("success");
24284
+ if (result.deduplicated) {
24285
+ await debug?.log("WARN", "SUBMIT_FIRST_INVOCATION", {
24286
+ message: `Workflow run ${workflowContext.workflowRunId} already exists. A new one isn't created.`,
24287
+ headers,
24288
+ requestPayload: workflowContext.requestPayload,
24289
+ url: workflowContext.url,
24290
+ messageId: result.messageId
24291
+ });
24292
+ return ok("workflow-run-already-exists");
24293
+ } else {
24294
+ await debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
24295
+ headers,
24296
+ requestPayload: workflowContext.requestPayload,
24297
+ url: workflowContext.url,
24298
+ messageId: result.messageId
24299
+ });
24300
+ return ok("success");
24301
+ }
24227
24302
  } catch (error) {
24228
24303
  const error_ = error;
24229
24304
  return err(error_);
@@ -24231,7 +24306,9 @@ var triggerFirstInvocation = async (workflowContext, retries, debug) => {
24231
24306
  };
24232
24307
  var triggerRouteFunction = async ({
24233
24308
  onCleanup,
24234
- onStep
24309
+ onStep,
24310
+ onCancel,
24311
+ debug
24235
24312
  }) => {
24236
24313
  try {
24237
24314
  await onStep();
@@ -24239,19 +24316,50 @@ var triggerRouteFunction = async ({
24239
24316
  return ok("workflow-finished");
24240
24317
  } catch (error) {
24241
24318
  const error_ = error;
24242
- return error_ instanceof QStashWorkflowAbort ? ok("step-finished") : err(error_);
24319
+ if (error instanceof import_qstash2.QstashError && error.status === 400) {
24320
+ await debug?.log("WARN", "RESPONSE_WORKFLOW", {
24321
+ message: `tried to append to a cancelled workflow. exiting without publishing.`,
24322
+ name: error.name,
24323
+ errorMessage: error.message
24324
+ });
24325
+ return ok("workflow-was-finished");
24326
+ } else if (!(error_ instanceof WorkflowAbort)) {
24327
+ return err(error_);
24328
+ } else if (error_.cancelWorkflow) {
24329
+ await onCancel();
24330
+ return ok("workflow-finished");
24331
+ } else {
24332
+ return ok("step-finished");
24333
+ }
24243
24334
  }
24244
24335
  };
24245
24336
  var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
24246
24337
  await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
24247
24338
  deletedWorkflowRunId: workflowContext.workflowRunId
24248
24339
  });
24249
- const result = await workflowContext.qstashClient.http.request({
24250
- path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
24251
- method: "DELETE",
24252
- parseResponseAsJson: false
24253
- });
24254
- await debug?.log("SUBMIT", "SUBMIT_CLEANUP", result);
24340
+ try {
24341
+ await workflowContext.qstashClient.http.request({
24342
+ path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
24343
+ method: "DELETE",
24344
+ parseResponseAsJson: false
24345
+ });
24346
+ await debug?.log(
24347
+ "SUBMIT",
24348
+ "SUBMIT_CLEANUP",
24349
+ `workflow run ${workflowContext.workflowRunId} deleted.`
24350
+ );
24351
+ return { deleted: true };
24352
+ } catch (error) {
24353
+ if (error instanceof import_qstash2.QstashError && error.status === 404) {
24354
+ await debug?.log("WARN", "SUBMIT_CLEANUP", {
24355
+ message: `Failed to remove workflow run ${workflowContext.workflowRunId} as it doesn't exist.`,
24356
+ name: error.name,
24357
+ errorMessage: error.message
24358
+ });
24359
+ return { deleted: false };
24360
+ }
24361
+ throw error;
24362
+ }
24255
24363
  };
24256
24364
  var recreateUserHeaders = (headers) => {
24257
24365
  const filteredHeaders = new Headers();
@@ -24267,15 +24375,32 @@ var recreateUserHeaders = (headers) => {
24267
24375
  var handleThirdPartyCallResult = async (request, requestPayload, client, workflowUrl, failureUrl, retries, debug) => {
24268
24376
  try {
24269
24377
  if (request.headers.get("Upstash-Workflow-Callback")) {
24270
- const callbackMessage = JSON.parse(requestPayload);
24378
+ let callbackPayload;
24379
+ if (requestPayload) {
24380
+ callbackPayload = requestPayload;
24381
+ } else {
24382
+ const workflowRunId2 = request.headers.get("upstash-workflow-runid");
24383
+ const messageId = request.headers.get("upstash-message-id");
24384
+ if (!workflowRunId2)
24385
+ throw new WorkflowError("workflow run id missing in context.call lazy fetch.");
24386
+ if (!messageId) throw new WorkflowError("message id missing in context.call lazy fetch.");
24387
+ const steps = await getSteps(client.http, workflowRunId2, messageId, debug);
24388
+ const failingStep = steps.find((step) => step.messageId === messageId);
24389
+ if (!failingStep)
24390
+ throw new WorkflowError(
24391
+ "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.`)
24392
+ );
24393
+ callbackPayload = atob(failingStep.body);
24394
+ }
24395
+ const callbackMessage = JSON.parse(callbackPayload);
24271
24396
  if (!(callbackMessage.status >= 200 && callbackMessage.status < 300) && callbackMessage.maxRetries && callbackMessage.retried !== callbackMessage.maxRetries) {
24272
24397
  await debug?.log("WARN", "SUBMIT_THIRD_PARTY_RESULT", {
24273
24398
  status: callbackMessage.status,
24274
- body: atob(callbackMessage.body)
24399
+ body: atob(callbackMessage.body ?? "")
24275
24400
  });
24276
24401
  console.warn(
24277
24402
  `Workflow Warning: "context.call" failed with status ${callbackMessage.status} and will retry (retried ${callbackMessage.retried ?? 0} out of ${callbackMessage.maxRetries} times). Error Message:
24278
- ${atob(callbackMessage.body)}`
24403
+ ${atob(callbackMessage.body ?? "")}`
24279
24404
  );
24280
24405
  return ok("call-will-retry");
24281
24406
  }
@@ -24309,7 +24434,7 @@ ${atob(callbackMessage.body)}`
24309
24434
  );
24310
24435
  const callResponse = {
24311
24436
  status: callbackMessage.status,
24312
- body: atob(callbackMessage.body),
24437
+ body: atob(callbackMessage.body ?? ""),
24313
24438
  header: callbackMessage.header
24314
24439
  };
24315
24440
  const callResultStep = {
@@ -24340,9 +24465,7 @@ ${atob(callbackMessage.body)}`
24340
24465
  } catch (error) {
24341
24466
  const isCallReturn = request.headers.get("Upstash-Workflow-Callback");
24342
24467
  return err(
24343
- new QStashWorkflowError(
24344
- `Error when handling call return (isCallReturn=${isCallReturn}): ${error}`
24345
- )
24468
+ new WorkflowError(`Error when handling call return (isCallReturn=${isCallReturn}): ${error}`)
24346
24469
  );
24347
24470
  }
24348
24471
  };
@@ -24350,7 +24473,8 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
24350
24473
  const baseHeaders = {
24351
24474
  [WORKFLOW_INIT_HEADER]: initHeaderValue,
24352
24475
  [WORKFLOW_ID_HEADER]: workflowRunId,
24353
- [WORKFLOW_URL_HEADER]: workflowUrl
24476
+ [WORKFLOW_URL_HEADER]: workflowUrl,
24477
+ [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody"
24354
24478
  };
24355
24479
  if (!step?.callUrl) {
24356
24480
  baseHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
@@ -24363,8 +24487,8 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
24363
24487
  }
24364
24488
  if (step?.callUrl) {
24365
24489
  baseHeaders["Upstash-Retries"] = callRetries?.toString() ?? "0";
24366
- baseHeaders[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete";
24367
- if (retries) {
24490
+ baseHeaders[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete,InitialBody";
24491
+ if (retries !== void 0) {
24368
24492
  baseHeaders["Upstash-Callback-Retries"] = retries.toString();
24369
24493
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
24370
24494
  }
@@ -24399,6 +24523,7 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
24399
24523
  "Upstash-Callback-Workflow-CallType": "fromCallback",
24400
24524
  "Upstash-Callback-Workflow-Init": "false",
24401
24525
  "Upstash-Callback-Workflow-Url": workflowUrl,
24526
+ "Upstash-Callback-Feature-Set": "LazyFetch,InitialBody",
24402
24527
  "Upstash-Callback-Forward-Upstash-Workflow-Callback": "true",
24403
24528
  "Upstash-Callback-Forward-Upstash-Workflow-StepId": step.stepId.toString(),
24404
24529
  "Upstash-Callback-Forward-Upstash-Workflow-StepName": step.stepName,
@@ -24447,7 +24572,7 @@ var verifyRequest = async (body, signature, verifier) => {
24447
24572
  throw new Error("Signature in `Upstash-Signature` header is not valid");
24448
24573
  }
24449
24574
  } catch (error) {
24450
- throw new QStashWorkflowError(
24575
+ throw new WorkflowError(
24451
24576
  `Failed to verify that the Workflow request comes from QStash: ${error}
24452
24577
 
24453
24578
  If signature is missing, trigger the workflow endpoint by publishing your request to QStash instead of calling it directly.
@@ -24487,14 +24612,14 @@ var AutoExecutor = class _AutoExecutor {
24487
24612
  *
24488
24613
  * If a function is already executing (this.executingStep), this
24489
24614
  * means that there is a nested step which is not allowed. In this
24490
- * case, addStep throws QStashWorkflowError.
24615
+ * case, addStep throws WorkflowError.
24491
24616
  *
24492
24617
  * @param stepInfo step plan to add
24493
24618
  * @returns result of the step function
24494
24619
  */
24495
24620
  async addStep(stepInfo) {
24496
24621
  if (this.executingStep) {
24497
- throw new QStashWorkflowError(
24622
+ throw new WorkflowError(
24498
24623
  `A step can not be run inside another step. Tried to run '${stepInfo.stepName}' inside '${this.executingStep}'`
24499
24624
  );
24500
24625
  }
@@ -24579,7 +24704,7 @@ var AutoExecutor = class _AutoExecutor {
24579
24704
  const sortedSteps = sortSteps(this.steps);
24580
24705
  const plannedParallelStepCount = sortedSteps[initialStepCount + this.planStepCount]?.concurrent;
24581
24706
  if (parallelCallState !== "first" && plannedParallelStepCount !== parallelSteps.length) {
24582
- throw new QStashWorkflowError(
24707
+ throw new WorkflowError(
24583
24708
  `Incompatible number of parallel steps when call state was '${parallelCallState}'. Expected ${parallelSteps.length}, got ${plannedParallelStepCount} from the request.`
24584
24709
  );
24585
24710
  }
@@ -24601,7 +24726,7 @@ var AutoExecutor = class _AutoExecutor {
24601
24726
  case "partial": {
24602
24727
  const planStep = this.steps.at(-1);
24603
24728
  if (!planStep || planStep.targetStep === void 0) {
24604
- throw new QStashWorkflowError(
24729
+ throw new WorkflowError(
24605
24730
  `There must be a last step and it should have targetStep larger than 0.Received: ${JSON.stringify(planStep)}`
24606
24731
  );
24607
24732
  }
@@ -24615,17 +24740,17 @@ var AutoExecutor = class _AutoExecutor {
24615
24740
  );
24616
24741
  await this.submitStepsToQStash([resultStep], [parallelStep]);
24617
24742
  } catch (error) {
24618
- if (error instanceof QStashWorkflowAbort) {
24743
+ if (error instanceof WorkflowAbort) {
24619
24744
  throw error;
24620
24745
  }
24621
- throw new QStashWorkflowError(
24746
+ throw new WorkflowError(
24622
24747
  `Error submitting steps to QStash in partial parallel step execution: ${error}`
24623
24748
  );
24624
24749
  }
24625
24750
  break;
24626
24751
  }
24627
24752
  case "discard": {
24628
- throw new QStashWorkflowAbort("discarded parallel");
24753
+ throw new WorkflowAbort("discarded parallel");
24629
24754
  }
24630
24755
  case "last": {
24631
24756
  const parallelResultSteps = sortedSteps.filter((step) => step.stepId >= initialStepCount).slice(0, parallelSteps.length);
@@ -24676,7 +24801,7 @@ var AutoExecutor = class _AutoExecutor {
24676
24801
  */
24677
24802
  async submitStepsToQStash(steps, lazySteps) {
24678
24803
  if (steps.length === 0) {
24679
- throw new QStashWorkflowError(
24804
+ throw new WorkflowError(
24680
24805
  `Unable to submit steps to QStash. Provided list is empty. Current step: ${this.stepCount}`
24681
24806
  );
24682
24807
  }
@@ -24716,7 +24841,7 @@ var AutoExecutor = class _AutoExecutor {
24716
24841
  method: "POST",
24717
24842
  parseResponseAsJson: false
24718
24843
  });
24719
- throw new QStashWorkflowAbort(steps[0].stepName, steps[0]);
24844
+ throw new WorkflowAbort(steps[0].stepName, steps[0]);
24720
24845
  }
24721
24846
  const result = await this.context.qstashClient.batchJSON(
24722
24847
  steps.map((singleStep, index) => {
@@ -24768,7 +24893,7 @@ var AutoExecutor = class _AutoExecutor {
24768
24893
  };
24769
24894
  })
24770
24895
  });
24771
- throw new QStashWorkflowAbort(steps[0].stepName, steps[0]);
24896
+ throw new WorkflowAbort(steps[0].stepName, steps[0]);
24772
24897
  }
24773
24898
  /**
24774
24899
  * Get the promise by executing the lazt steps list. If there is a single
@@ -24793,7 +24918,7 @@ var AutoExecutor = class _AutoExecutor {
24793
24918
  } else if (Array.isArray(result) && lazyStepList.length === result.length && index < lazyStepList.length) {
24794
24919
  return result[index];
24795
24920
  } else {
24796
- throw new QStashWorkflowError(
24921
+ throw new WorkflowError(
24797
24922
  `Unexpected parallel call result while executing step ${index}: '${result}'. Expected ${lazyStepList.length} many items`
24798
24923
  );
24799
24924
  }
@@ -24805,12 +24930,12 @@ var AutoExecutor = class _AutoExecutor {
24805
24930
  };
24806
24931
  var validateStep = (lazyStep, stepFromRequest) => {
24807
24932
  if (lazyStep.stepName !== stepFromRequest.stepName) {
24808
- throw new QStashWorkflowError(
24933
+ throw new WorkflowError(
24809
24934
  `Incompatible step name. Expected '${lazyStep.stepName}', got '${stepFromRequest.stepName}' from the request`
24810
24935
  );
24811
24936
  }
24812
24937
  if (lazyStep.stepType !== stepFromRequest.stepType) {
24813
- throw new QStashWorkflowError(
24938
+ throw new WorkflowError(
24814
24939
  `Incompatible step type. Expected '${lazyStep.stepType}', got '${stepFromRequest.stepType}' from the request`
24815
24940
  );
24816
24941
  }
@@ -24821,12 +24946,12 @@ var validateParallelSteps = (lazySteps, stepsFromRequest) => {
24821
24946
  validateStep(lazySteps[index], stepFromRequest);
24822
24947
  }
24823
24948
  } catch (error) {
24824
- if (error instanceof QStashWorkflowError) {
24949
+ if (error instanceof WorkflowError) {
24825
24950
  const lazyStepNames = lazySteps.map((lazyStep) => lazyStep.stepName);
24826
24951
  const lazyStepTypes = lazySteps.map((lazyStep) => lazyStep.stepType);
24827
24952
  const requestStepNames = stepsFromRequest.map((step) => step.stepName);
24828
24953
  const requestStepTypes = stepsFromRequest.map((step) => step.stepType);
24829
- throw new QStashWorkflowError(
24954
+ throw new WorkflowError(
24830
24955
  `Incompatible steps detected in parallel execution: ${error.message}
24831
24956
  > Step Names from the request: ${JSON.stringify(requestStepNames)}
24832
24957
  Step Types from the request: ${JSON.stringify(requestStepTypes)}
@@ -24939,10 +25064,6 @@ var WorkflowContext = class {
24939
25064
  * headers of the initial request
24940
25065
  */
24941
25066
  headers;
24942
- /**
24943
- * initial payload as a raw string
24944
- */
24945
- rawInitialPayload;
24946
25067
  /**
24947
25068
  * Map of environment variables and their values.
24948
25069
  *
@@ -24977,7 +25098,6 @@ var WorkflowContext = class {
24977
25098
  failureUrl,
24978
25099
  debug,
24979
25100
  initialPayload,
24980
- rawInitialPayload,
24981
25101
  env,
24982
25102
  retries
24983
25103
  }) {
@@ -24988,7 +25108,6 @@ var WorkflowContext = class {
24988
25108
  this.failureUrl = failureUrl;
24989
25109
  this.headers = headers;
24990
25110
  this.requestPayload = initialPayload;
24991
- this.rawInitialPayload = rawInitialPayload ?? JSON.stringify(this.requestPayload);
24992
25111
  this.env = env ?? {};
24993
25112
  this.retries = retries ?? DEFAULT_RETRIES;
24994
25113
  this.executor = new AutoExecutor(this, this.steps, debug);
@@ -25009,7 +25128,7 @@ var WorkflowContext = class {
25009
25128
  * const [result1, result2] = await Promise.all([
25010
25129
  * context.run("step 1", () => {
25011
25130
  * return "result1"
25012
- * })
25131
+ * }),
25013
25132
  * context.run("step 2", async () => {
25014
25133
  * return await fetchResults()
25015
25134
  * })
@@ -25027,6 +25146,10 @@ var WorkflowContext = class {
25027
25146
  /**
25028
25147
  * Stops the execution for the duration provided.
25029
25148
  *
25149
+ * ```typescript
25150
+ * await context.sleep('sleep1', 3) // wait for three seconds
25151
+ * ```
25152
+ *
25030
25153
  * @param stepName
25031
25154
  * @param duration sleep duration in seconds
25032
25155
  * @returns undefined
@@ -25037,6 +25160,10 @@ var WorkflowContext = class {
25037
25160
  /**
25038
25161
  * Stops the execution until the date time provided.
25039
25162
  *
25163
+ * ```typescript
25164
+ * await context.sleepUntil('sleep1', Date.now() / 1000 + 3) // wait for three seconds
25165
+ * ```
25166
+ *
25040
25167
  * @param stepName
25041
25168
  * @param datetime time to sleep until. Can be provided as a number (in unix seconds),
25042
25169
  * as a Date object or a string (passed to `new Date(datetimeString)`)
@@ -25060,7 +25187,7 @@ var WorkflowContext = class {
25060
25187
  * const { status, body } = await context.call<string>(
25061
25188
  * "post call step",
25062
25189
  * {
25063
- * url: `https://www.some-endpoint.com/api`,
25190
+ * url: "https://www.some-endpoint.com/api",
25064
25191
  * method: "POST",
25065
25192
  * body: "my-payload"
25066
25193
  * }
@@ -25114,45 +25241,43 @@ var WorkflowContext = class {
25114
25241
  }
25115
25242
  }
25116
25243
  /**
25117
- * Makes the workflow run wait until a notify request is sent or until the
25118
- * timeout ends
25244
+ * Pauses workflow execution until a specific event occurs or a timeout is reached.
25119
25245
  *
25120
- * ```ts
25121
- * const { eventData, timeout } = await context.waitForEvent(
25122
- * "wait for event step",
25123
- * "my-event-id",
25124
- * 100 // timeout after 100 seconds
25125
- * );
25126
- * ```
25246
+ *```ts
25247
+ * const result = await workflow.waitForEvent("payment-confirmed", {
25248
+ * timeout: "5m"
25249
+ * });
25250
+ *```
25127
25251
  *
25128
- * To notify a waiting workflow run, you can use the notify method:
25252
+ * To notify a waiting workflow:
25129
25253
  *
25130
25254
  * ```ts
25131
25255
  * import { Client } from "@upstash/workflow";
25132
25256
  *
25133
- * const client = new Client({ token: });
25257
+ * const client = new Client({ token: "<QSTASH_TOKEN>" });
25134
25258
  *
25135
25259
  * await client.notify({
25136
- * eventId: "my-event-id",
25137
- * eventData: "eventData"
25260
+ * eventId: "payment.confirmed",
25261
+ * data: {
25262
+ * amount: 99.99,
25263
+ * currency: "USD"
25264
+ * }
25138
25265
  * })
25139
25266
  * ```
25140
25267
  *
25268
+ * Alternatively, you can use the `context.notify` method.
25269
+ *
25141
25270
  * @param stepName
25142
- * @param eventId event id to wake up the waiting workflow run
25143
- * @param timeout timeout duration in seconds
25144
- * @returns wait response as `{ timeout: boolean, eventData: unknown }`.
25145
- * timeout is true if the wait times out, if notified it is false. eventData
25146
- * is the value passed to `client.notify`.
25271
+ * @param eventId - Unique identifier for the event to wait for
25272
+ * @param options - Configuration options.
25273
+ * @returns `{ timeout: boolean, eventData: unknown }`.
25274
+ * The `timeout` property specifies if the workflow has timed out. The `eventData`
25275
+ * is the data passed when notifying this workflow of an event.
25147
25276
  */
25148
- async waitForEvent(stepName, eventId, timeout) {
25149
- const result = await this.addStep(
25150
- new LazyWaitForEventStep(
25151
- stepName,
25152
- eventId,
25153
- typeof timeout === "string" ? timeout : `${timeout}s`
25154
- )
25155
- );
25277
+ async waitForEvent(stepName, eventId, options = {}) {
25278
+ const { timeout = "7d" } = options;
25279
+ const timeoutStr = typeof timeout === "string" ? timeout : `${timeout}s`;
25280
+ const result = await this.addStep(new LazyWaitForEventStep(stepName, eventId, timeoutStr));
25156
25281
  try {
25157
25282
  return {
25158
25283
  ...result,
@@ -25162,6 +25287,27 @@ var WorkflowContext = class {
25162
25287
  return result;
25163
25288
  }
25164
25289
  }
25290
+ /**
25291
+ * Notify workflow runs waiting for an event
25292
+ *
25293
+ * ```ts
25294
+ * const { eventId, eventData, notifyResponse } = await context.notify(
25295
+ * "notify step", "event-id", "event-data"
25296
+ * );
25297
+ * ```
25298
+ *
25299
+ * Upon `context.notify`, the workflow runs waiting for the given eventId (context.waitForEvent)
25300
+ * will receive the given event data and resume execution.
25301
+ *
25302
+ * The response includes the same eventId and eventData. Additionally, there is
25303
+ * a notifyResponse field which contains a list of `Waiter` objects, each corresponding
25304
+ * to a notified workflow run.
25305
+ *
25306
+ * @param stepName
25307
+ * @param eventId event id to notify
25308
+ * @param eventData event data to notify with
25309
+ * @returns notify response which has event id, event data and list of waiters which were notified
25310
+ */
25165
25311
  async notify(stepName, eventId, eventData) {
25166
25312
  const result = await this.addStep(
25167
25313
  new LazyNotifyStep(stepName, eventId, eventData, this.qstashClient.http)
@@ -25175,6 +25321,15 @@ var WorkflowContext = class {
25175
25321
  return result;
25176
25322
  }
25177
25323
  }
25324
+ /**
25325
+ * Cancel the current workflow run
25326
+ *
25327
+ * Will throw WorkflowAbort to stop workflow execution.
25328
+ * Shouldn't be inside try/catch.
25329
+ */
25330
+ async cancel() {
25331
+ throw new WorkflowAbort("cancel", void 0, true);
25332
+ }
25178
25333
  /**
25179
25334
  * Adds steps to the executor. Needed so that it can be overwritten in
25180
25335
  * DisabledWorkflowContext.
@@ -25215,7 +25370,8 @@ var WorkflowLogger = class _WorkflowLogger {
25215
25370
  }
25216
25371
  writeToConsole(logEntry) {
25217
25372
  const JSON_SPACING = 2;
25218
- console.log(JSON.stringify(logEntry, void 0, JSON_SPACING));
25373
+ const logMethod = logEntry.logLevel === "ERROR" ? console.error : logEntry.logLevel === "WARN" ? console.warn : console.log;
25374
+ logMethod(JSON.stringify(logEntry, void 0, JSON_SPACING));
25219
25375
  }
25220
25376
  shouldLog(level) {
25221
25377
  return LOG_LEVELS.indexOf(level) >= LOG_LEVELS.indexOf(this.options.logLevel);
@@ -25233,11 +25389,13 @@ var WorkflowLogger = class _WorkflowLogger {
25233
25389
  };
25234
25390
 
25235
25391
  // src/utils.ts
25236
- var import_node_crypto = __toESM(require("crypto"));
25237
25392
  var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
25238
25393
  var NANOID_LENGTH = 21;
25394
+ function getRandomInt() {
25395
+ return Math.floor(Math.random() * NANOID_CHARS.length);
25396
+ }
25239
25397
  function nanoid() {
25240
- return [...import_node_crypto.default.getRandomValues(new Uint8Array(NANOID_LENGTH))].map((x) => NANOID_CHARS[x % NANOID_CHARS.length]).join("");
25398
+ return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
25241
25399
  }
25242
25400
  function getWorkflowRunId(id) {
25243
25401
  return `wfr_${id ?? nanoid()}`;
@@ -25255,6 +25413,63 @@ function decodeBase64(base64) {
25255
25413
  }
25256
25414
  }
25257
25415
 
25416
+ // src/serve/authorization.ts
25417
+ var import_qstash3 = require("@upstash/qstash");
25418
+ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
25419
+ static disabledMessage = "disabled-qstash-worklfow-run";
25420
+ /**
25421
+ * overwrite the WorkflowContext.addStep method to always raise WorkflowAbort
25422
+ * error in order to stop the execution whenever we encounter a step.
25423
+ *
25424
+ * @param _step
25425
+ */
25426
+ async addStep(_step) {
25427
+ throw new WorkflowAbort(_DisabledWorkflowContext.disabledMessage);
25428
+ }
25429
+ /**
25430
+ * overwrite cancel method to do nothing
25431
+ */
25432
+ async cancel() {
25433
+ return;
25434
+ }
25435
+ /**
25436
+ * copies the passed context to create a DisabledWorkflowContext. Then, runs the
25437
+ * route function with the new context.
25438
+ *
25439
+ * - returns "run-ended" if there are no steps found or
25440
+ * if the auth failed and user called `return`
25441
+ * - returns "step-found" if DisabledWorkflowContext.addStep is called.
25442
+ * - if there is another error, returns the error.
25443
+ *
25444
+ * @param routeFunction
25445
+ */
25446
+ static async tryAuthentication(routeFunction, context) {
25447
+ const disabledContext = new _DisabledWorkflowContext({
25448
+ qstashClient: new import_qstash3.Client({
25449
+ baseUrl: "disabled-client",
25450
+ token: "disabled-client"
25451
+ }),
25452
+ workflowRunId: context.workflowRunId,
25453
+ headers: context.headers,
25454
+ steps: [],
25455
+ url: context.url,
25456
+ failureUrl: context.failureUrl,
25457
+ initialPayload: context.requestPayload,
25458
+ env: context.env,
25459
+ retries: context.retries
25460
+ });
25461
+ try {
25462
+ await routeFunction(disabledContext);
25463
+ } catch (error) {
25464
+ if (error instanceof WorkflowAbort && error.stepName === this.disabledMessage) {
25465
+ return ok("step-found");
25466
+ }
25467
+ return err(error);
25468
+ }
25469
+ return ok("run-ended");
25470
+ }
25471
+ };
25472
+
25258
25473
  // src/workflow-parser.ts
25259
25474
  var getPayload = async (request) => {
25260
25475
  try {
@@ -25263,8 +25478,8 @@ var getPayload = async (request) => {
25263
25478
  return;
25264
25479
  }
25265
25480
  };
25266
- var parsePayload = async (rawPayload, debug) => {
25267
- const [encodedInitialPayload, ...encodedSteps] = JSON.parse(rawPayload);
25481
+ var processRawSteps = (rawSteps) => {
25482
+ const [encodedInitialPayload, ...encodedSteps] = rawSteps;
25268
25483
  const rawInitialPayload = decodeBase64(encodedInitialPayload.body);
25269
25484
  const initialStep = {
25270
25485
  stepId: 0,
@@ -25274,27 +25489,21 @@ var parsePayload = async (rawPayload, debug) => {
25274
25489
  concurrent: NO_CONCURRENCY
25275
25490
  };
25276
25491
  const stepsToDecode = encodedSteps.filter((step) => step.callType === "step");
25277
- const otherSteps = await Promise.all(
25278
- stepsToDecode.map(async (rawStep) => {
25279
- const step = JSON.parse(decodeBase64(rawStep.body));
25280
- try {
25281
- step.out = JSON.parse(step.out);
25282
- } catch {
25283
- await debug?.log("WARN", "ENDPOINT_START", {
25284
- message: "failed while parsing out field of step",
25285
- step
25286
- });
25287
- }
25288
- if (step.waitEventId) {
25289
- const newOut = {
25290
- eventData: step.out ? decodeBase64(step.out) : void 0,
25291
- timeout: step.waitTimeout ?? false
25292
- };
25293
- step.out = newOut;
25294
- }
25295
- return step;
25296
- })
25297
- );
25492
+ const otherSteps = stepsToDecode.map((rawStep) => {
25493
+ const step = JSON.parse(decodeBase64(rawStep.body));
25494
+ try {
25495
+ step.out = JSON.parse(step.out);
25496
+ } catch {
25497
+ }
25498
+ if (step.waitEventId) {
25499
+ const newOut = {
25500
+ eventData: step.out ? decodeBase64(step.out) : void 0,
25501
+ timeout: step.waitTimeout ?? false
25502
+ };
25503
+ step.out = newOut;
25504
+ }
25505
+ return step;
25506
+ });
25298
25507
  const steps = [initialStep, ...otherSteps];
25299
25508
  return {
25300
25509
  rawInitialPayload,
@@ -25342,20 +25551,20 @@ var validateRequest = (request) => {
25342
25551
  const versionHeader = request.headers.get(WORKFLOW_PROTOCOL_VERSION_HEADER);
25343
25552
  const isFirstInvocation = !versionHeader;
25344
25553
  if (!isFirstInvocation && versionHeader !== WORKFLOW_PROTOCOL_VERSION) {
25345
- throw new QStashWorkflowError(
25554
+ throw new WorkflowError(
25346
25555
  `Incompatible workflow sdk protocol version. Expected ${WORKFLOW_PROTOCOL_VERSION}, got ${versionHeader} from the request.`
25347
25556
  );
25348
25557
  }
25349
25558
  const workflowRunId = isFirstInvocation ? getWorkflowRunId() : request.headers.get(WORKFLOW_ID_HEADER) ?? "";
25350
25559
  if (workflowRunId.length === 0) {
25351
- throw new QStashWorkflowError("Couldn't get workflow id from header");
25560
+ throw new WorkflowError("Couldn't get workflow id from header");
25352
25561
  }
25353
25562
  return {
25354
25563
  isFirstInvocation,
25355
25564
  workflowRunId
25356
25565
  };
25357
25566
  };
25358
- var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
25567
+ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requester, messageId, debug) => {
25359
25568
  if (isFirstInvocation) {
25360
25569
  return {
25361
25570
  rawInitialPayload: requestPayload ?? "",
@@ -25363,10 +25572,18 @@ var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
25363
25572
  isLastDuplicate: false
25364
25573
  };
25365
25574
  } else {
25575
+ let rawSteps;
25366
25576
  if (!requestPayload) {
25367
- throw new QStashWorkflowError("Only first call can have an empty body");
25577
+ await debug?.log(
25578
+ "INFO",
25579
+ "ENDPOINT_START",
25580
+ "request payload is empty, steps will be fetched from QStash."
25581
+ );
25582
+ rawSteps = await getSteps(requester, workflowRunId, messageId, debug);
25583
+ } else {
25584
+ rawSteps = JSON.parse(requestPayload);
25368
25585
  }
25369
- const { rawInitialPayload, steps } = await parsePayload(requestPayload, debug);
25586
+ const { rawInitialPayload, steps } = processRawSteps(rawSteps);
25370
25587
  const isLastDuplicate = await checkIfLastOneIsDuplicate(steps, debug);
25371
25588
  const deduplicatedSteps = deduplicateSteps(steps);
25372
25589
  return {
@@ -25376,13 +25593,13 @@ var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
25376
25593
  };
25377
25594
  }
25378
25595
  };
25379
- var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, failureFunction, debug) => {
25596
+ var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, debug) => {
25380
25597
  if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
25381
25598
  return ok("not-failure-callback");
25382
25599
  }
25383
25600
  if (!failureFunction) {
25384
25601
  return err(
25385
- new QStashWorkflowError(
25602
+ new WorkflowError(
25386
25603
  "Workflow endpoint is called to handle a failure, but a failureFunction is not provided in serve options. Either provide a failureUrl or a failureFunction."
25387
25604
  )
25388
25605
  );
@@ -25393,92 +25610,48 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
25393
25610
  );
25394
25611
  const decodedBody = body ? decodeBase64(body) : "{}";
25395
25612
  const errorPayload = JSON.parse(decodedBody);
25396
- const {
25397
- rawInitialPayload,
25398
- steps,
25399
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
25400
- isLastDuplicate: _isLastDuplicate
25401
- } = await parseRequest(decodeBase64(sourceBody), false, debug);
25402
25613
  const workflowContext = new WorkflowContext({
25403
25614
  qstashClient,
25404
25615
  workflowRunId,
25405
- initialPayload: initialPayloadParser(rawInitialPayload),
25406
- rawInitialPayload,
25616
+ initialPayload: initialPayloadParser(decodeBase64(sourceBody)),
25407
25617
  headers: recreateUserHeaders(new Headers(sourceHeader)),
25408
- steps,
25618
+ steps: [],
25409
25619
  url,
25410
25620
  failureUrl: url,
25411
25621
  debug
25412
25622
  });
25413
- await failureFunction(workflowContext, status, errorPayload.message, header);
25623
+ const authCheck = await DisabledWorkflowContext.tryAuthentication(
25624
+ routeFunction,
25625
+ workflowContext
25626
+ );
25627
+ if (authCheck.isErr()) {
25628
+ await debug?.log("ERROR", "ERROR", { error: authCheck.error.message });
25629
+ throw authCheck.error;
25630
+ } else if (authCheck.value === "run-ended") {
25631
+ return err(new WorkflowError("Not authorized to run the failure function."));
25632
+ }
25633
+ await failureFunction({
25634
+ context: workflowContext,
25635
+ failStatus: status,
25636
+ failResponse: errorPayload.message,
25637
+ failHeaders: header
25638
+ });
25414
25639
  } catch (error) {
25415
25640
  return err(error);
25416
25641
  }
25417
25642
  return ok("is-failure-callback");
25418
25643
  };
25419
25644
 
25420
- // src/serve/authorization.ts
25421
- var import_qstash2 = require("@upstash/qstash");
25422
- var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
25423
- static disabledMessage = "disabled-qstash-worklfow-run";
25424
- /**
25425
- * overwrite the WorkflowContext.addStep method to always raise QStashWorkflowAbort
25426
- * error in order to stop the execution whenever we encounter a step.
25427
- *
25428
- * @param _step
25429
- */
25430
- async addStep(_step) {
25431
- throw new QStashWorkflowAbort(_DisabledWorkflowContext.disabledMessage);
25432
- }
25433
- /**
25434
- * copies the passed context to create a DisabledWorkflowContext. Then, runs the
25435
- * route function with the new context.
25436
- *
25437
- * - returns "run-ended" if there are no steps found or
25438
- * if the auth failed and user called `return`
25439
- * - returns "step-found" if DisabledWorkflowContext.addStep is called.
25440
- * - if there is another error, returns the error.
25441
- *
25442
- * @param routeFunction
25443
- */
25444
- static async tryAuthentication(routeFunction, context) {
25445
- const disabledContext = new _DisabledWorkflowContext({
25446
- qstashClient: new import_qstash2.Client({
25447
- baseUrl: "disabled-client",
25448
- token: "disabled-client"
25449
- }),
25450
- workflowRunId: context.workflowRunId,
25451
- headers: context.headers,
25452
- steps: [],
25453
- url: context.url,
25454
- failureUrl: context.failureUrl,
25455
- initialPayload: context.requestPayload,
25456
- rawInitialPayload: context.rawInitialPayload,
25457
- env: context.env,
25458
- retries: context.retries
25459
- });
25460
- try {
25461
- await routeFunction(disabledContext);
25462
- } catch (error) {
25463
- if (error instanceof QStashWorkflowAbort && error.stepName === this.disabledMessage) {
25464
- return ok("step-found");
25465
- }
25466
- return err(error);
25467
- }
25468
- return ok("run-ended");
25469
- }
25470
- };
25471
-
25472
25645
  // src/serve/options.ts
25473
- var import_qstash3 = require("@upstash/qstash");
25474
25646
  var import_qstash4 = require("@upstash/qstash");
25647
+ var import_qstash5 = require("@upstash/qstash");
25475
25648
  var processOptions = (options) => {
25476
25649
  const environment = options?.env ?? (typeof process === "undefined" ? {} : process.env);
25477
25650
  const receiverEnvironmentVariablesSet = Boolean(
25478
25651
  environment.QSTASH_CURRENT_SIGNING_KEY && environment.QSTASH_NEXT_SIGNING_KEY
25479
25652
  );
25480
25653
  return {
25481
- qstashClient: new import_qstash4.Client({
25654
+ qstashClient: new import_qstash5.Client({
25482
25655
  baseUrl: environment.QSTASH_URL,
25483
25656
  token: environment.QSTASH_TOKEN
25484
25657
  }),
@@ -25499,7 +25672,7 @@ var processOptions = (options) => {
25499
25672
  throw error;
25500
25673
  }
25501
25674
  },
25502
- receiver: receiverEnvironmentVariablesSet ? new import_qstash3.Receiver({
25675
+ receiver: receiverEnvironmentVariablesSet ? new import_qstash4.Receiver({
25503
25676
  currentSigningKey: environment.QSTASH_CURRENT_SIGNING_KEY,
25504
25677
  nextSigningKey: environment.QSTASH_NEXT_SIGNING_KEY
25505
25678
  }) : void 0,
@@ -25561,6 +25734,9 @@ var serve = (routeFunction, options) => {
25561
25734
  const { rawInitialPayload, steps, isLastDuplicate } = await parseRequest(
25562
25735
  requestPayload,
25563
25736
  isFirstInvocation,
25737
+ workflowRunId,
25738
+ qstashClient.http,
25739
+ request.headers.get("upstash-message-id"),
25564
25740
  debug
25565
25741
  );
25566
25742
  if (isLastDuplicate) {
@@ -25571,6 +25747,7 @@ var serve = (routeFunction, options) => {
25571
25747
  requestPayload,
25572
25748
  qstashClient,
25573
25749
  initialPayloadParser,
25750
+ routeFunction,
25574
25751
  failureFunction
25575
25752
  );
25576
25753
  if (failureCheck.isErr()) {
@@ -25583,7 +25760,6 @@ var serve = (routeFunction, options) => {
25583
25760
  qstashClient,
25584
25761
  workflowRunId,
25585
25762
  initialPayload: initialPayloadParser(rawInitialPayload),
25586
- rawInitialPayload,
25587
25763
  headers: recreateUserHeaders(request.headers),
25588
25764
  steps,
25589
25765
  url: workflowUrl,
@@ -25621,7 +25797,11 @@ var serve = (routeFunction, options) => {
25621
25797
  onStep: async () => routeFunction(workflowContext),
25622
25798
  onCleanup: async () => {
25623
25799
  await triggerWorkflowDelete(workflowContext, debug);
25624
- }
25800
+ },
25801
+ onCancel: async () => {
25802
+ await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
25803
+ },
25804
+ debug
25625
25805
  });
25626
25806
  if (result.isErr()) {
25627
25807
  await debug?.log("ERROR", "ERROR", { error: result.error.message });
@@ -25647,7 +25827,7 @@ var serve = (routeFunction, options) => {
25647
25827
  };
25648
25828
 
25649
25829
  // src/client/index.ts
25650
- var import_qstash5 = require("@upstash/qstash");
25830
+ var import_qstash6 = require("@upstash/qstash");
25651
25831
 
25652
25832
  // platforms/express.ts
25653
25833
  var import_express = __toESM(require_express2());