@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/index.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,24 +15,16 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
31
21
  var src_exports = {};
32
22
  __export(src_exports, {
33
23
  Client: () => Client3,
34
- QStashWorkflowAbort: () => QStashWorkflowAbort,
35
- QStashWorkflowError: () => QStashWorkflowError,
36
24
  StepTypes: () => StepTypes,
25
+ WorkflowAbort: () => WorkflowAbort,
37
26
  WorkflowContext: () => WorkflowContext,
27
+ WorkflowError: () => WorkflowError,
38
28
  WorkflowLogger: () => WorkflowLogger,
39
29
  serve: () => serve
40
30
  });
@@ -42,22 +32,33 @@ module.exports = __toCommonJS(src_exports);
42
32
 
43
33
  // src/error.ts
44
34
  var import_qstash = require("@upstash/qstash");
45
- var QStashWorkflowError = class extends import_qstash.QstashError {
35
+ var WorkflowError = class extends import_qstash.QstashError {
46
36
  constructor(message) {
47
37
  super(message);
48
- this.name = "QStashWorkflowError";
38
+ this.name = "WorkflowError";
49
39
  }
50
40
  };
51
- var QStashWorkflowAbort = class extends Error {
41
+ var WorkflowAbort = class extends Error {
52
42
  stepInfo;
53
43
  stepName;
54
- constructor(stepName, stepInfo) {
44
+ /**
45
+ * whether workflow is to be canceled on abort
46
+ */
47
+ cancelWorkflow;
48
+ /**
49
+ *
50
+ * @param stepName name of the aborting step
51
+ * @param stepInfo step information
52
+ * @param cancelWorkflow
53
+ */
54
+ constructor(stepName, stepInfo, cancelWorkflow = false) {
55
55
  super(
56
56
  `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}'.`
57
57
  );
58
- this.name = "QStashWorkflowAbort";
58
+ this.name = "WorkflowAbort";
59
59
  this.stepName = stepName;
60
60
  this.stepInfo = stepInfo;
61
+ this.cancelWorkflow = cancelWorkflow;
61
62
  }
62
63
  };
63
64
  var formatWorkflowError = (error) => {
@@ -86,6 +87,44 @@ var makeGetWaitersRequest = async (requester, eventId) => {
86
87
  });
87
88
  return result;
88
89
  };
90
+ var makeCancelRequest = async (requester, workflowRunId) => {
91
+ await requester.request({
92
+ path: ["v2", "workflows", "runs", `${workflowRunId}?cancel=true`],
93
+ method: "DELETE",
94
+ parseResponseAsJson: false
95
+ });
96
+ return true;
97
+ };
98
+ var getSteps = async (requester, workflowRunId, messageId, debug) => {
99
+ try {
100
+ const steps = await requester.request({
101
+ path: ["v2", "workflows", "runs", workflowRunId],
102
+ parseResponseAsJson: true
103
+ });
104
+ if (!messageId) {
105
+ await debug?.log("INFO", "ENDPOINT_START", {
106
+ message: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
107
+ });
108
+ return steps;
109
+ } else {
110
+ const index = steps.findIndex((item) => item.messageId === messageId);
111
+ if (index === -1) {
112
+ return [];
113
+ }
114
+ const filteredSteps = steps.slice(0, index + 1);
115
+ await debug?.log("INFO", "ENDPOINT_START", {
116
+ message: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
117
+ });
118
+ return filteredSteps;
119
+ }
120
+ } catch (error) {
121
+ await debug?.log("ERROR", "ERROR", {
122
+ message: "failed while fetching steps.",
123
+ error
124
+ });
125
+ throw new WorkflowError(`Failed while pulling steps. ${error}`);
126
+ }
127
+ };
89
128
 
90
129
  // src/context/steps.ts
91
130
  var BaseLazyStep = class {
@@ -700,6 +739,7 @@ var StepTypes = [
700
739
  ];
701
740
 
702
741
  // src/workflow-requests.ts
742
+ var import_qstash2 = require("@upstash/qstash");
703
743
  var triggerFirstInvocation = async (workflowContext, retries, debug) => {
704
744
  const { headers } = getHeaders(
705
745
  "true",
@@ -710,20 +750,32 @@ var triggerFirstInvocation = async (workflowContext, retries, debug) => {
710
750
  workflowContext.failureUrl,
711
751
  retries
712
752
  );
713
- await debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
714
- headers,
715
- requestPayload: workflowContext.requestPayload,
716
- url: workflowContext.url
717
- });
718
753
  try {
719
754
  const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
720
- await workflowContext.qstashClient.publish({
755
+ const result = await workflowContext.qstashClient.publish({
721
756
  headers,
722
757
  method: "POST",
723
758
  body,
724
759
  url: workflowContext.url
725
760
  });
726
- return ok("success");
761
+ if (result.deduplicated) {
762
+ await debug?.log("WARN", "SUBMIT_FIRST_INVOCATION", {
763
+ message: `Workflow run ${workflowContext.workflowRunId} already exists. A new one isn't created.`,
764
+ headers,
765
+ requestPayload: workflowContext.requestPayload,
766
+ url: workflowContext.url,
767
+ messageId: result.messageId
768
+ });
769
+ return ok("workflow-run-already-exists");
770
+ } else {
771
+ await debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
772
+ headers,
773
+ requestPayload: workflowContext.requestPayload,
774
+ url: workflowContext.url,
775
+ messageId: result.messageId
776
+ });
777
+ return ok("success");
778
+ }
727
779
  } catch (error) {
728
780
  const error_ = error;
729
781
  return err(error_);
@@ -731,7 +783,9 @@ var triggerFirstInvocation = async (workflowContext, retries, debug) => {
731
783
  };
732
784
  var triggerRouteFunction = async ({
733
785
  onCleanup,
734
- onStep
786
+ onStep,
787
+ onCancel,
788
+ debug
735
789
  }) => {
736
790
  try {
737
791
  await onStep();
@@ -739,19 +793,50 @@ var triggerRouteFunction = async ({
739
793
  return ok("workflow-finished");
740
794
  } catch (error) {
741
795
  const error_ = error;
742
- return error_ instanceof QStashWorkflowAbort ? ok("step-finished") : err(error_);
796
+ if (error instanceof import_qstash2.QstashError && error.status === 400) {
797
+ await debug?.log("WARN", "RESPONSE_WORKFLOW", {
798
+ message: `tried to append to a cancelled workflow. exiting without publishing.`,
799
+ name: error.name,
800
+ errorMessage: error.message
801
+ });
802
+ return ok("workflow-was-finished");
803
+ } else if (!(error_ instanceof WorkflowAbort)) {
804
+ return err(error_);
805
+ } else if (error_.cancelWorkflow) {
806
+ await onCancel();
807
+ return ok("workflow-finished");
808
+ } else {
809
+ return ok("step-finished");
810
+ }
743
811
  }
744
812
  };
745
813
  var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
746
814
  await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
747
815
  deletedWorkflowRunId: workflowContext.workflowRunId
748
816
  });
749
- const result = await workflowContext.qstashClient.http.request({
750
- path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
751
- method: "DELETE",
752
- parseResponseAsJson: false
753
- });
754
- await debug?.log("SUBMIT", "SUBMIT_CLEANUP", result);
817
+ try {
818
+ await workflowContext.qstashClient.http.request({
819
+ path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
820
+ method: "DELETE",
821
+ parseResponseAsJson: false
822
+ });
823
+ await debug?.log(
824
+ "SUBMIT",
825
+ "SUBMIT_CLEANUP",
826
+ `workflow run ${workflowContext.workflowRunId} deleted.`
827
+ );
828
+ return { deleted: true };
829
+ } catch (error) {
830
+ if (error instanceof import_qstash2.QstashError && error.status === 404) {
831
+ await debug?.log("WARN", "SUBMIT_CLEANUP", {
832
+ message: `Failed to remove workflow run ${workflowContext.workflowRunId} as it doesn't exist.`,
833
+ name: error.name,
834
+ errorMessage: error.message
835
+ });
836
+ return { deleted: false };
837
+ }
838
+ throw error;
839
+ }
755
840
  };
756
841
  var recreateUserHeaders = (headers) => {
757
842
  const filteredHeaders = new Headers();
@@ -767,15 +852,32 @@ var recreateUserHeaders = (headers) => {
767
852
  var handleThirdPartyCallResult = async (request, requestPayload, client, workflowUrl, failureUrl, retries, debug) => {
768
853
  try {
769
854
  if (request.headers.get("Upstash-Workflow-Callback")) {
770
- const callbackMessage = JSON.parse(requestPayload);
855
+ let callbackPayload;
856
+ if (requestPayload) {
857
+ callbackPayload = requestPayload;
858
+ } else {
859
+ const workflowRunId2 = request.headers.get("upstash-workflow-runid");
860
+ const messageId = request.headers.get("upstash-message-id");
861
+ if (!workflowRunId2)
862
+ throw new WorkflowError("workflow run id missing in context.call lazy fetch.");
863
+ if (!messageId) throw new WorkflowError("message id missing in context.call lazy fetch.");
864
+ const steps = await getSteps(client.http, workflowRunId2, messageId, debug);
865
+ const failingStep = steps.find((step) => step.messageId === messageId);
866
+ if (!failingStep)
867
+ throw new WorkflowError(
868
+ "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.`)
869
+ );
870
+ callbackPayload = atob(failingStep.body);
871
+ }
872
+ const callbackMessage = JSON.parse(callbackPayload);
771
873
  if (!(callbackMessage.status >= 200 && callbackMessage.status < 300) && callbackMessage.maxRetries && callbackMessage.retried !== callbackMessage.maxRetries) {
772
874
  await debug?.log("WARN", "SUBMIT_THIRD_PARTY_RESULT", {
773
875
  status: callbackMessage.status,
774
- body: atob(callbackMessage.body)
876
+ body: atob(callbackMessage.body ?? "")
775
877
  });
776
878
  console.warn(
777
879
  `Workflow Warning: "context.call" failed with status ${callbackMessage.status} and will retry (retried ${callbackMessage.retried ?? 0} out of ${callbackMessage.maxRetries} times). Error Message:
778
- ${atob(callbackMessage.body)}`
880
+ ${atob(callbackMessage.body ?? "")}`
779
881
  );
780
882
  return ok("call-will-retry");
781
883
  }
@@ -809,7 +911,7 @@ ${atob(callbackMessage.body)}`
809
911
  );
810
912
  const callResponse = {
811
913
  status: callbackMessage.status,
812
- body: atob(callbackMessage.body),
914
+ body: atob(callbackMessage.body ?? ""),
813
915
  header: callbackMessage.header
814
916
  };
815
917
  const callResultStep = {
@@ -840,9 +942,7 @@ ${atob(callbackMessage.body)}`
840
942
  } catch (error) {
841
943
  const isCallReturn = request.headers.get("Upstash-Workflow-Callback");
842
944
  return err(
843
- new QStashWorkflowError(
844
- `Error when handling call return (isCallReturn=${isCallReturn}): ${error}`
845
- )
945
+ new WorkflowError(`Error when handling call return (isCallReturn=${isCallReturn}): ${error}`)
846
946
  );
847
947
  }
848
948
  };
@@ -850,7 +950,8 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
850
950
  const baseHeaders = {
851
951
  [WORKFLOW_INIT_HEADER]: initHeaderValue,
852
952
  [WORKFLOW_ID_HEADER]: workflowRunId,
853
- [WORKFLOW_URL_HEADER]: workflowUrl
953
+ [WORKFLOW_URL_HEADER]: workflowUrl,
954
+ [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody"
854
955
  };
855
956
  if (!step?.callUrl) {
856
957
  baseHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
@@ -863,8 +964,8 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
863
964
  }
864
965
  if (step?.callUrl) {
865
966
  baseHeaders["Upstash-Retries"] = callRetries?.toString() ?? "0";
866
- baseHeaders[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete";
867
- if (retries) {
967
+ baseHeaders[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete,InitialBody";
968
+ if (retries !== void 0) {
868
969
  baseHeaders["Upstash-Callback-Retries"] = retries.toString();
869
970
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
870
971
  }
@@ -899,6 +1000,7 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
899
1000
  "Upstash-Callback-Workflow-CallType": "fromCallback",
900
1001
  "Upstash-Callback-Workflow-Init": "false",
901
1002
  "Upstash-Callback-Workflow-Url": workflowUrl,
1003
+ "Upstash-Callback-Feature-Set": "LazyFetch,InitialBody",
902
1004
  "Upstash-Callback-Forward-Upstash-Workflow-Callback": "true",
903
1005
  "Upstash-Callback-Forward-Upstash-Workflow-StepId": step.stepId.toString(),
904
1006
  "Upstash-Callback-Forward-Upstash-Workflow-StepName": step.stepName,
@@ -947,7 +1049,7 @@ var verifyRequest = async (body, signature, verifier) => {
947
1049
  throw new Error("Signature in `Upstash-Signature` header is not valid");
948
1050
  }
949
1051
  } catch (error) {
950
- throw new QStashWorkflowError(
1052
+ throw new WorkflowError(
951
1053
  `Failed to verify that the Workflow request comes from QStash: ${error}
952
1054
 
953
1055
  If signature is missing, trigger the workflow endpoint by publishing your request to QStash instead of calling it directly.
@@ -987,14 +1089,14 @@ var AutoExecutor = class _AutoExecutor {
987
1089
  *
988
1090
  * If a function is already executing (this.executingStep), this
989
1091
  * means that there is a nested step which is not allowed. In this
990
- * case, addStep throws QStashWorkflowError.
1092
+ * case, addStep throws WorkflowError.
991
1093
  *
992
1094
  * @param stepInfo step plan to add
993
1095
  * @returns result of the step function
994
1096
  */
995
1097
  async addStep(stepInfo) {
996
1098
  if (this.executingStep) {
997
- throw new QStashWorkflowError(
1099
+ throw new WorkflowError(
998
1100
  `A step can not be run inside another step. Tried to run '${stepInfo.stepName}' inside '${this.executingStep}'`
999
1101
  );
1000
1102
  }
@@ -1079,7 +1181,7 @@ var AutoExecutor = class _AutoExecutor {
1079
1181
  const sortedSteps = sortSteps(this.steps);
1080
1182
  const plannedParallelStepCount = sortedSteps[initialStepCount + this.planStepCount]?.concurrent;
1081
1183
  if (parallelCallState !== "first" && plannedParallelStepCount !== parallelSteps.length) {
1082
- throw new QStashWorkflowError(
1184
+ throw new WorkflowError(
1083
1185
  `Incompatible number of parallel steps when call state was '${parallelCallState}'. Expected ${parallelSteps.length}, got ${plannedParallelStepCount} from the request.`
1084
1186
  );
1085
1187
  }
@@ -1101,7 +1203,7 @@ var AutoExecutor = class _AutoExecutor {
1101
1203
  case "partial": {
1102
1204
  const planStep = this.steps.at(-1);
1103
1205
  if (!planStep || planStep.targetStep === void 0) {
1104
- throw new QStashWorkflowError(
1206
+ throw new WorkflowError(
1105
1207
  `There must be a last step and it should have targetStep larger than 0.Received: ${JSON.stringify(planStep)}`
1106
1208
  );
1107
1209
  }
@@ -1115,17 +1217,17 @@ var AutoExecutor = class _AutoExecutor {
1115
1217
  );
1116
1218
  await this.submitStepsToQStash([resultStep], [parallelStep]);
1117
1219
  } catch (error) {
1118
- if (error instanceof QStashWorkflowAbort) {
1220
+ if (error instanceof WorkflowAbort) {
1119
1221
  throw error;
1120
1222
  }
1121
- throw new QStashWorkflowError(
1223
+ throw new WorkflowError(
1122
1224
  `Error submitting steps to QStash in partial parallel step execution: ${error}`
1123
1225
  );
1124
1226
  }
1125
1227
  break;
1126
1228
  }
1127
1229
  case "discard": {
1128
- throw new QStashWorkflowAbort("discarded parallel");
1230
+ throw new WorkflowAbort("discarded parallel");
1129
1231
  }
1130
1232
  case "last": {
1131
1233
  const parallelResultSteps = sortedSteps.filter((step) => step.stepId >= initialStepCount).slice(0, parallelSteps.length);
@@ -1176,7 +1278,7 @@ var AutoExecutor = class _AutoExecutor {
1176
1278
  */
1177
1279
  async submitStepsToQStash(steps, lazySteps) {
1178
1280
  if (steps.length === 0) {
1179
- throw new QStashWorkflowError(
1281
+ throw new WorkflowError(
1180
1282
  `Unable to submit steps to QStash. Provided list is empty. Current step: ${this.stepCount}`
1181
1283
  );
1182
1284
  }
@@ -1216,7 +1318,7 @@ var AutoExecutor = class _AutoExecutor {
1216
1318
  method: "POST",
1217
1319
  parseResponseAsJson: false
1218
1320
  });
1219
- throw new QStashWorkflowAbort(steps[0].stepName, steps[0]);
1321
+ throw new WorkflowAbort(steps[0].stepName, steps[0]);
1220
1322
  }
1221
1323
  const result = await this.context.qstashClient.batchJSON(
1222
1324
  steps.map((singleStep, index) => {
@@ -1268,7 +1370,7 @@ var AutoExecutor = class _AutoExecutor {
1268
1370
  };
1269
1371
  })
1270
1372
  });
1271
- throw new QStashWorkflowAbort(steps[0].stepName, steps[0]);
1373
+ throw new WorkflowAbort(steps[0].stepName, steps[0]);
1272
1374
  }
1273
1375
  /**
1274
1376
  * Get the promise by executing the lazt steps list. If there is a single
@@ -1293,7 +1395,7 @@ var AutoExecutor = class _AutoExecutor {
1293
1395
  } else if (Array.isArray(result) && lazyStepList.length === result.length && index < lazyStepList.length) {
1294
1396
  return result[index];
1295
1397
  } else {
1296
- throw new QStashWorkflowError(
1398
+ throw new WorkflowError(
1297
1399
  `Unexpected parallel call result while executing step ${index}: '${result}'. Expected ${lazyStepList.length} many items`
1298
1400
  );
1299
1401
  }
@@ -1305,12 +1407,12 @@ var AutoExecutor = class _AutoExecutor {
1305
1407
  };
1306
1408
  var validateStep = (lazyStep, stepFromRequest) => {
1307
1409
  if (lazyStep.stepName !== stepFromRequest.stepName) {
1308
- throw new QStashWorkflowError(
1410
+ throw new WorkflowError(
1309
1411
  `Incompatible step name. Expected '${lazyStep.stepName}', got '${stepFromRequest.stepName}' from the request`
1310
1412
  );
1311
1413
  }
1312
1414
  if (lazyStep.stepType !== stepFromRequest.stepType) {
1313
- throw new QStashWorkflowError(
1415
+ throw new WorkflowError(
1314
1416
  `Incompatible step type. Expected '${lazyStep.stepType}', got '${stepFromRequest.stepType}' from the request`
1315
1417
  );
1316
1418
  }
@@ -1321,12 +1423,12 @@ var validateParallelSteps = (lazySteps, stepsFromRequest) => {
1321
1423
  validateStep(lazySteps[index], stepFromRequest);
1322
1424
  }
1323
1425
  } catch (error) {
1324
- if (error instanceof QStashWorkflowError) {
1426
+ if (error instanceof WorkflowError) {
1325
1427
  const lazyStepNames = lazySteps.map((lazyStep) => lazyStep.stepName);
1326
1428
  const lazyStepTypes = lazySteps.map((lazyStep) => lazyStep.stepType);
1327
1429
  const requestStepNames = stepsFromRequest.map((step) => step.stepName);
1328
1430
  const requestStepTypes = stepsFromRequest.map((step) => step.stepType);
1329
- throw new QStashWorkflowError(
1431
+ throw new WorkflowError(
1330
1432
  `Incompatible steps detected in parallel execution: ${error.message}
1331
1433
  > Step Names from the request: ${JSON.stringify(requestStepNames)}
1332
1434
  Step Types from the request: ${JSON.stringify(requestStepTypes)}
@@ -1439,10 +1541,6 @@ var WorkflowContext = class {
1439
1541
  * headers of the initial request
1440
1542
  */
1441
1543
  headers;
1442
- /**
1443
- * initial payload as a raw string
1444
- */
1445
- rawInitialPayload;
1446
1544
  /**
1447
1545
  * Map of environment variables and their values.
1448
1546
  *
@@ -1477,7 +1575,6 @@ var WorkflowContext = class {
1477
1575
  failureUrl,
1478
1576
  debug,
1479
1577
  initialPayload,
1480
- rawInitialPayload,
1481
1578
  env,
1482
1579
  retries
1483
1580
  }) {
@@ -1488,7 +1585,6 @@ var WorkflowContext = class {
1488
1585
  this.failureUrl = failureUrl;
1489
1586
  this.headers = headers;
1490
1587
  this.requestPayload = initialPayload;
1491
- this.rawInitialPayload = rawInitialPayload ?? JSON.stringify(this.requestPayload);
1492
1588
  this.env = env ?? {};
1493
1589
  this.retries = retries ?? DEFAULT_RETRIES;
1494
1590
  this.executor = new AutoExecutor(this, this.steps, debug);
@@ -1509,7 +1605,7 @@ var WorkflowContext = class {
1509
1605
  * const [result1, result2] = await Promise.all([
1510
1606
  * context.run("step 1", () => {
1511
1607
  * return "result1"
1512
- * })
1608
+ * }),
1513
1609
  * context.run("step 2", async () => {
1514
1610
  * return await fetchResults()
1515
1611
  * })
@@ -1527,6 +1623,10 @@ var WorkflowContext = class {
1527
1623
  /**
1528
1624
  * Stops the execution for the duration provided.
1529
1625
  *
1626
+ * ```typescript
1627
+ * await context.sleep('sleep1', 3) // wait for three seconds
1628
+ * ```
1629
+ *
1530
1630
  * @param stepName
1531
1631
  * @param duration sleep duration in seconds
1532
1632
  * @returns undefined
@@ -1537,6 +1637,10 @@ var WorkflowContext = class {
1537
1637
  /**
1538
1638
  * Stops the execution until the date time provided.
1539
1639
  *
1640
+ * ```typescript
1641
+ * await context.sleepUntil('sleep1', Date.now() / 1000 + 3) // wait for three seconds
1642
+ * ```
1643
+ *
1540
1644
  * @param stepName
1541
1645
  * @param datetime time to sleep until. Can be provided as a number (in unix seconds),
1542
1646
  * as a Date object or a string (passed to `new Date(datetimeString)`)
@@ -1560,7 +1664,7 @@ var WorkflowContext = class {
1560
1664
  * const { status, body } = await context.call<string>(
1561
1665
  * "post call step",
1562
1666
  * {
1563
- * url: `https://www.some-endpoint.com/api`,
1667
+ * url: "https://www.some-endpoint.com/api",
1564
1668
  * method: "POST",
1565
1669
  * body: "my-payload"
1566
1670
  * }
@@ -1614,45 +1718,43 @@ var WorkflowContext = class {
1614
1718
  }
1615
1719
  }
1616
1720
  /**
1617
- * Makes the workflow run wait until a notify request is sent or until the
1618
- * timeout ends
1721
+ * Pauses workflow execution until a specific event occurs or a timeout is reached.
1619
1722
  *
1620
- * ```ts
1621
- * const { eventData, timeout } = await context.waitForEvent(
1622
- * "wait for event step",
1623
- * "my-event-id",
1624
- * 100 // timeout after 100 seconds
1625
- * );
1626
- * ```
1723
+ *```ts
1724
+ * const result = await workflow.waitForEvent("payment-confirmed", {
1725
+ * timeout: "5m"
1726
+ * });
1727
+ *```
1627
1728
  *
1628
- * To notify a waiting workflow run, you can use the notify method:
1729
+ * To notify a waiting workflow:
1629
1730
  *
1630
1731
  * ```ts
1631
1732
  * import { Client } from "@upstash/workflow";
1632
1733
  *
1633
- * const client = new Client({ token: });
1734
+ * const client = new Client({ token: "<QSTASH_TOKEN>" });
1634
1735
  *
1635
1736
  * await client.notify({
1636
- * eventId: "my-event-id",
1637
- * eventData: "eventData"
1737
+ * eventId: "payment.confirmed",
1738
+ * data: {
1739
+ * amount: 99.99,
1740
+ * currency: "USD"
1741
+ * }
1638
1742
  * })
1639
1743
  * ```
1640
1744
  *
1745
+ * Alternatively, you can use the `context.notify` method.
1746
+ *
1641
1747
  * @param stepName
1642
- * @param eventId event id to wake up the waiting workflow run
1643
- * @param timeout timeout duration in seconds
1644
- * @returns wait response as `{ timeout: boolean, eventData: unknown }`.
1645
- * timeout is true if the wait times out, if notified it is false. eventData
1646
- * is the value passed to `client.notify`.
1748
+ * @param eventId - Unique identifier for the event to wait for
1749
+ * @param options - Configuration options.
1750
+ * @returns `{ timeout: boolean, eventData: unknown }`.
1751
+ * The `timeout` property specifies if the workflow has timed out. The `eventData`
1752
+ * is the data passed when notifying this workflow of an event.
1647
1753
  */
1648
- async waitForEvent(stepName, eventId, timeout) {
1649
- const result = await this.addStep(
1650
- new LazyWaitForEventStep(
1651
- stepName,
1652
- eventId,
1653
- typeof timeout === "string" ? timeout : `${timeout}s`
1654
- )
1655
- );
1754
+ async waitForEvent(stepName, eventId, options = {}) {
1755
+ const { timeout = "7d" } = options;
1756
+ const timeoutStr = typeof timeout === "string" ? timeout : `${timeout}s`;
1757
+ const result = await this.addStep(new LazyWaitForEventStep(stepName, eventId, timeoutStr));
1656
1758
  try {
1657
1759
  return {
1658
1760
  ...result,
@@ -1662,6 +1764,27 @@ var WorkflowContext = class {
1662
1764
  return result;
1663
1765
  }
1664
1766
  }
1767
+ /**
1768
+ * Notify workflow runs waiting for an event
1769
+ *
1770
+ * ```ts
1771
+ * const { eventId, eventData, notifyResponse } = await context.notify(
1772
+ * "notify step", "event-id", "event-data"
1773
+ * );
1774
+ * ```
1775
+ *
1776
+ * Upon `context.notify`, the workflow runs waiting for the given eventId (context.waitForEvent)
1777
+ * will receive the given event data and resume execution.
1778
+ *
1779
+ * The response includes the same eventId and eventData. Additionally, there is
1780
+ * a notifyResponse field which contains a list of `Waiter` objects, each corresponding
1781
+ * to a notified workflow run.
1782
+ *
1783
+ * @param stepName
1784
+ * @param eventId event id to notify
1785
+ * @param eventData event data to notify with
1786
+ * @returns notify response which has event id, event data and list of waiters which were notified
1787
+ */
1665
1788
  async notify(stepName, eventId, eventData) {
1666
1789
  const result = await this.addStep(
1667
1790
  new LazyNotifyStep(stepName, eventId, eventData, this.qstashClient.http)
@@ -1675,6 +1798,15 @@ var WorkflowContext = class {
1675
1798
  return result;
1676
1799
  }
1677
1800
  }
1801
+ /**
1802
+ * Cancel the current workflow run
1803
+ *
1804
+ * Will throw WorkflowAbort to stop workflow execution.
1805
+ * Shouldn't be inside try/catch.
1806
+ */
1807
+ async cancel() {
1808
+ throw new WorkflowAbort("cancel", void 0, true);
1809
+ }
1678
1810
  /**
1679
1811
  * Adds steps to the executor. Needed so that it can be overwritten in
1680
1812
  * DisabledWorkflowContext.
@@ -1715,7 +1847,8 @@ var WorkflowLogger = class _WorkflowLogger {
1715
1847
  }
1716
1848
  writeToConsole(logEntry) {
1717
1849
  const JSON_SPACING = 2;
1718
- console.log(JSON.stringify(logEntry, void 0, JSON_SPACING));
1850
+ const logMethod = logEntry.logLevel === "ERROR" ? console.error : logEntry.logLevel === "WARN" ? console.warn : console.log;
1851
+ logMethod(JSON.stringify(logEntry, void 0, JSON_SPACING));
1719
1852
  }
1720
1853
  shouldLog(level) {
1721
1854
  return LOG_LEVELS.indexOf(level) >= LOG_LEVELS.indexOf(this.options.logLevel);
@@ -1733,11 +1866,13 @@ var WorkflowLogger = class _WorkflowLogger {
1733
1866
  };
1734
1867
 
1735
1868
  // src/utils.ts
1736
- var import_node_crypto = __toESM(require("crypto"));
1737
1869
  var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
1738
1870
  var NANOID_LENGTH = 21;
1871
+ function getRandomInt() {
1872
+ return Math.floor(Math.random() * NANOID_CHARS.length);
1873
+ }
1739
1874
  function nanoid() {
1740
- return [...import_node_crypto.default.getRandomValues(new Uint8Array(NANOID_LENGTH))].map((x) => NANOID_CHARS[x % NANOID_CHARS.length]).join("");
1875
+ return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
1741
1876
  }
1742
1877
  function getWorkflowRunId(id) {
1743
1878
  return `wfr_${id ?? nanoid()}`;
@@ -1755,6 +1890,63 @@ function decodeBase64(base64) {
1755
1890
  }
1756
1891
  }
1757
1892
 
1893
+ // src/serve/authorization.ts
1894
+ var import_qstash3 = require("@upstash/qstash");
1895
+ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
1896
+ static disabledMessage = "disabled-qstash-worklfow-run";
1897
+ /**
1898
+ * overwrite the WorkflowContext.addStep method to always raise WorkflowAbort
1899
+ * error in order to stop the execution whenever we encounter a step.
1900
+ *
1901
+ * @param _step
1902
+ */
1903
+ async addStep(_step) {
1904
+ throw new WorkflowAbort(_DisabledWorkflowContext.disabledMessage);
1905
+ }
1906
+ /**
1907
+ * overwrite cancel method to do nothing
1908
+ */
1909
+ async cancel() {
1910
+ return;
1911
+ }
1912
+ /**
1913
+ * copies the passed context to create a DisabledWorkflowContext. Then, runs the
1914
+ * route function with the new context.
1915
+ *
1916
+ * - returns "run-ended" if there are no steps found or
1917
+ * if the auth failed and user called `return`
1918
+ * - returns "step-found" if DisabledWorkflowContext.addStep is called.
1919
+ * - if there is another error, returns the error.
1920
+ *
1921
+ * @param routeFunction
1922
+ */
1923
+ static async tryAuthentication(routeFunction, context) {
1924
+ const disabledContext = new _DisabledWorkflowContext({
1925
+ qstashClient: new import_qstash3.Client({
1926
+ baseUrl: "disabled-client",
1927
+ token: "disabled-client"
1928
+ }),
1929
+ workflowRunId: context.workflowRunId,
1930
+ headers: context.headers,
1931
+ steps: [],
1932
+ url: context.url,
1933
+ failureUrl: context.failureUrl,
1934
+ initialPayload: context.requestPayload,
1935
+ env: context.env,
1936
+ retries: context.retries
1937
+ });
1938
+ try {
1939
+ await routeFunction(disabledContext);
1940
+ } catch (error) {
1941
+ if (error instanceof WorkflowAbort && error.stepName === this.disabledMessage) {
1942
+ return ok("step-found");
1943
+ }
1944
+ return err(error);
1945
+ }
1946
+ return ok("run-ended");
1947
+ }
1948
+ };
1949
+
1758
1950
  // src/workflow-parser.ts
1759
1951
  var getPayload = async (request) => {
1760
1952
  try {
@@ -1763,8 +1955,8 @@ var getPayload = async (request) => {
1763
1955
  return;
1764
1956
  }
1765
1957
  };
1766
- var parsePayload = async (rawPayload, debug) => {
1767
- const [encodedInitialPayload, ...encodedSteps] = JSON.parse(rawPayload);
1958
+ var processRawSteps = (rawSteps) => {
1959
+ const [encodedInitialPayload, ...encodedSteps] = rawSteps;
1768
1960
  const rawInitialPayload = decodeBase64(encodedInitialPayload.body);
1769
1961
  const initialStep = {
1770
1962
  stepId: 0,
@@ -1774,27 +1966,21 @@ var parsePayload = async (rawPayload, debug) => {
1774
1966
  concurrent: NO_CONCURRENCY
1775
1967
  };
1776
1968
  const stepsToDecode = encodedSteps.filter((step) => step.callType === "step");
1777
- const otherSteps = await Promise.all(
1778
- stepsToDecode.map(async (rawStep) => {
1779
- const step = JSON.parse(decodeBase64(rawStep.body));
1780
- try {
1781
- step.out = JSON.parse(step.out);
1782
- } catch {
1783
- await debug?.log("WARN", "ENDPOINT_START", {
1784
- message: "failed while parsing out field of step",
1785
- step
1786
- });
1787
- }
1788
- if (step.waitEventId) {
1789
- const newOut = {
1790
- eventData: step.out ? decodeBase64(step.out) : void 0,
1791
- timeout: step.waitTimeout ?? false
1792
- };
1793
- step.out = newOut;
1794
- }
1795
- return step;
1796
- })
1797
- );
1969
+ const otherSteps = stepsToDecode.map((rawStep) => {
1970
+ const step = JSON.parse(decodeBase64(rawStep.body));
1971
+ try {
1972
+ step.out = JSON.parse(step.out);
1973
+ } catch {
1974
+ }
1975
+ if (step.waitEventId) {
1976
+ const newOut = {
1977
+ eventData: step.out ? decodeBase64(step.out) : void 0,
1978
+ timeout: step.waitTimeout ?? false
1979
+ };
1980
+ step.out = newOut;
1981
+ }
1982
+ return step;
1983
+ });
1798
1984
  const steps = [initialStep, ...otherSteps];
1799
1985
  return {
1800
1986
  rawInitialPayload,
@@ -1842,20 +2028,20 @@ var validateRequest = (request) => {
1842
2028
  const versionHeader = request.headers.get(WORKFLOW_PROTOCOL_VERSION_HEADER);
1843
2029
  const isFirstInvocation = !versionHeader;
1844
2030
  if (!isFirstInvocation && versionHeader !== WORKFLOW_PROTOCOL_VERSION) {
1845
- throw new QStashWorkflowError(
2031
+ throw new WorkflowError(
1846
2032
  `Incompatible workflow sdk protocol version. Expected ${WORKFLOW_PROTOCOL_VERSION}, got ${versionHeader} from the request.`
1847
2033
  );
1848
2034
  }
1849
2035
  const workflowRunId = isFirstInvocation ? getWorkflowRunId() : request.headers.get(WORKFLOW_ID_HEADER) ?? "";
1850
2036
  if (workflowRunId.length === 0) {
1851
- throw new QStashWorkflowError("Couldn't get workflow id from header");
2037
+ throw new WorkflowError("Couldn't get workflow id from header");
1852
2038
  }
1853
2039
  return {
1854
2040
  isFirstInvocation,
1855
2041
  workflowRunId
1856
2042
  };
1857
2043
  };
1858
- var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
2044
+ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requester, messageId, debug) => {
1859
2045
  if (isFirstInvocation) {
1860
2046
  return {
1861
2047
  rawInitialPayload: requestPayload ?? "",
@@ -1863,10 +2049,18 @@ var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
1863
2049
  isLastDuplicate: false
1864
2050
  };
1865
2051
  } else {
2052
+ let rawSteps;
1866
2053
  if (!requestPayload) {
1867
- throw new QStashWorkflowError("Only first call can have an empty body");
2054
+ await debug?.log(
2055
+ "INFO",
2056
+ "ENDPOINT_START",
2057
+ "request payload is empty, steps will be fetched from QStash."
2058
+ );
2059
+ rawSteps = await getSteps(requester, workflowRunId, messageId, debug);
2060
+ } else {
2061
+ rawSteps = JSON.parse(requestPayload);
1868
2062
  }
1869
- const { rawInitialPayload, steps } = await parsePayload(requestPayload, debug);
2063
+ const { rawInitialPayload, steps } = processRawSteps(rawSteps);
1870
2064
  const isLastDuplicate = await checkIfLastOneIsDuplicate(steps, debug);
1871
2065
  const deduplicatedSteps = deduplicateSteps(steps);
1872
2066
  return {
@@ -1876,13 +2070,13 @@ var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
1876
2070
  };
1877
2071
  }
1878
2072
  };
1879
- var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, failureFunction, debug) => {
2073
+ var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, debug) => {
1880
2074
  if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
1881
2075
  return ok("not-failure-callback");
1882
2076
  }
1883
2077
  if (!failureFunction) {
1884
2078
  return err(
1885
- new QStashWorkflowError(
2079
+ new WorkflowError(
1886
2080
  "Workflow endpoint is called to handle a failure, but a failureFunction is not provided in serve options. Either provide a failureUrl or a failureFunction."
1887
2081
  )
1888
2082
  );
@@ -1893,92 +2087,48 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
1893
2087
  );
1894
2088
  const decodedBody = body ? decodeBase64(body) : "{}";
1895
2089
  const errorPayload = JSON.parse(decodedBody);
1896
- const {
1897
- rawInitialPayload,
1898
- steps,
1899
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1900
- isLastDuplicate: _isLastDuplicate
1901
- } = await parseRequest(decodeBase64(sourceBody), false, debug);
1902
2090
  const workflowContext = new WorkflowContext({
1903
2091
  qstashClient,
1904
2092
  workflowRunId,
1905
- initialPayload: initialPayloadParser(rawInitialPayload),
1906
- rawInitialPayload,
2093
+ initialPayload: initialPayloadParser(decodeBase64(sourceBody)),
1907
2094
  headers: recreateUserHeaders(new Headers(sourceHeader)),
1908
- steps,
2095
+ steps: [],
1909
2096
  url,
1910
2097
  failureUrl: url,
1911
2098
  debug
1912
2099
  });
1913
- await failureFunction(workflowContext, status, errorPayload.message, header);
2100
+ const authCheck = await DisabledWorkflowContext.tryAuthentication(
2101
+ routeFunction,
2102
+ workflowContext
2103
+ );
2104
+ if (authCheck.isErr()) {
2105
+ await debug?.log("ERROR", "ERROR", { error: authCheck.error.message });
2106
+ throw authCheck.error;
2107
+ } else if (authCheck.value === "run-ended") {
2108
+ return err(new WorkflowError("Not authorized to run the failure function."));
2109
+ }
2110
+ await failureFunction({
2111
+ context: workflowContext,
2112
+ failStatus: status,
2113
+ failResponse: errorPayload.message,
2114
+ failHeaders: header
2115
+ });
1914
2116
  } catch (error) {
1915
2117
  return err(error);
1916
2118
  }
1917
2119
  return ok("is-failure-callback");
1918
2120
  };
1919
2121
 
1920
- // src/serve/authorization.ts
1921
- var import_qstash2 = require("@upstash/qstash");
1922
- var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
1923
- static disabledMessage = "disabled-qstash-worklfow-run";
1924
- /**
1925
- * overwrite the WorkflowContext.addStep method to always raise QStashWorkflowAbort
1926
- * error in order to stop the execution whenever we encounter a step.
1927
- *
1928
- * @param _step
1929
- */
1930
- async addStep(_step) {
1931
- throw new QStashWorkflowAbort(_DisabledWorkflowContext.disabledMessage);
1932
- }
1933
- /**
1934
- * copies the passed context to create a DisabledWorkflowContext. Then, runs the
1935
- * route function with the new context.
1936
- *
1937
- * - returns "run-ended" if there are no steps found or
1938
- * if the auth failed and user called `return`
1939
- * - returns "step-found" if DisabledWorkflowContext.addStep is called.
1940
- * - if there is another error, returns the error.
1941
- *
1942
- * @param routeFunction
1943
- */
1944
- static async tryAuthentication(routeFunction, context) {
1945
- const disabledContext = new _DisabledWorkflowContext({
1946
- qstashClient: new import_qstash2.Client({
1947
- baseUrl: "disabled-client",
1948
- token: "disabled-client"
1949
- }),
1950
- workflowRunId: context.workflowRunId,
1951
- headers: context.headers,
1952
- steps: [],
1953
- url: context.url,
1954
- failureUrl: context.failureUrl,
1955
- initialPayload: context.requestPayload,
1956
- rawInitialPayload: context.rawInitialPayload,
1957
- env: context.env,
1958
- retries: context.retries
1959
- });
1960
- try {
1961
- await routeFunction(disabledContext);
1962
- } catch (error) {
1963
- if (error instanceof QStashWorkflowAbort && error.stepName === this.disabledMessage) {
1964
- return ok("step-found");
1965
- }
1966
- return err(error);
1967
- }
1968
- return ok("run-ended");
1969
- }
1970
- };
1971
-
1972
2122
  // src/serve/options.ts
1973
- var import_qstash3 = require("@upstash/qstash");
1974
2123
  var import_qstash4 = require("@upstash/qstash");
2124
+ var import_qstash5 = require("@upstash/qstash");
1975
2125
  var processOptions = (options) => {
1976
2126
  const environment = options?.env ?? (typeof process === "undefined" ? {} : process.env);
1977
2127
  const receiverEnvironmentVariablesSet = Boolean(
1978
2128
  environment.QSTASH_CURRENT_SIGNING_KEY && environment.QSTASH_NEXT_SIGNING_KEY
1979
2129
  );
1980
2130
  return {
1981
- qstashClient: new import_qstash4.Client({
2131
+ qstashClient: new import_qstash5.Client({
1982
2132
  baseUrl: environment.QSTASH_URL,
1983
2133
  token: environment.QSTASH_TOKEN
1984
2134
  }),
@@ -1999,7 +2149,7 @@ var processOptions = (options) => {
1999
2149
  throw error;
2000
2150
  }
2001
2151
  },
2002
- receiver: receiverEnvironmentVariablesSet ? new import_qstash3.Receiver({
2152
+ receiver: receiverEnvironmentVariablesSet ? new import_qstash4.Receiver({
2003
2153
  currentSigningKey: environment.QSTASH_CURRENT_SIGNING_KEY,
2004
2154
  nextSigningKey: environment.QSTASH_NEXT_SIGNING_KEY
2005
2155
  }) : void 0,
@@ -2061,6 +2211,9 @@ var serve = (routeFunction, options) => {
2061
2211
  const { rawInitialPayload, steps, isLastDuplicate } = await parseRequest(
2062
2212
  requestPayload,
2063
2213
  isFirstInvocation,
2214
+ workflowRunId,
2215
+ qstashClient.http,
2216
+ request.headers.get("upstash-message-id"),
2064
2217
  debug
2065
2218
  );
2066
2219
  if (isLastDuplicate) {
@@ -2071,6 +2224,7 @@ var serve = (routeFunction, options) => {
2071
2224
  requestPayload,
2072
2225
  qstashClient,
2073
2226
  initialPayloadParser,
2227
+ routeFunction,
2074
2228
  failureFunction
2075
2229
  );
2076
2230
  if (failureCheck.isErr()) {
@@ -2083,7 +2237,6 @@ var serve = (routeFunction, options) => {
2083
2237
  qstashClient,
2084
2238
  workflowRunId,
2085
2239
  initialPayload: initialPayloadParser(rawInitialPayload),
2086
- rawInitialPayload,
2087
2240
  headers: recreateUserHeaders(request.headers),
2088
2241
  steps,
2089
2242
  url: workflowUrl,
@@ -2121,7 +2274,11 @@ var serve = (routeFunction, options) => {
2121
2274
  onStep: async () => routeFunction(workflowContext),
2122
2275
  onCleanup: async () => {
2123
2276
  await triggerWorkflowDelete(workflowContext, debug);
2124
- }
2277
+ },
2278
+ onCancel: async () => {
2279
+ await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
2280
+ },
2281
+ debug
2125
2282
  });
2126
2283
  if (result.isErr()) {
2127
2284
  await debug?.log("ERROR", "ERROR", { error: result.error.message });
@@ -2147,35 +2304,93 @@ var serve = (routeFunction, options) => {
2147
2304
  };
2148
2305
 
2149
2306
  // src/client/index.ts
2150
- var import_qstash5 = require("@upstash/qstash");
2307
+ var import_qstash6 = require("@upstash/qstash");
2151
2308
  var Client3 = class {
2152
2309
  client;
2153
2310
  constructor(clientConfig) {
2154
2311
  if (!clientConfig.token) {
2155
- console.warn("[Upstash Workflow] url or the token is not set. client will not work.");
2312
+ console.error(
2313
+ "QStash token is required for Upstash Workflow!\n\nTo fix this:\n1. Get your token from the Upstash Console (https://console.upstash.com/qstash)\n2. Initialize the workflow client with:\n\n const client = new Client({\n token: '<YOUR_QSTASH_TOKEN>'\n });"
2314
+ );
2156
2315
  }
2157
- this.client = new import_qstash5.Client(clientConfig);
2316
+ this.client = new import_qstash6.Client(clientConfig);
2158
2317
  }
2159
2318
  /**
2160
2319
  * Cancel an ongoing workflow
2161
2320
  *
2321
+ * Returns true if workflow is canceled succesfully. Otherwise, throws error.
2322
+ *
2323
+ * There are multiple ways you can cancel workflows:
2324
+ * - pass one or more workflow run ids to cancel them
2325
+ * - pass a workflow url to cancel all runs starting with this url
2326
+ * - cancel all pending or active workflow runs
2327
+ *
2328
+ * ### Cancel a set of workflow runs
2329
+ *
2162
2330
  * ```ts
2163
- * import { Client } from "@upstash/workflow";
2331
+ * // cancel a single workflow
2332
+ * await client.cancel({ ids: "<WORKFLOW_RUN_ID>" })
2164
2333
  *
2165
- * const client = new Client({ token: "<QSTASH_TOKEN>" })
2166
- * await client.cancel({ workflowRunId: "<WORKFLOW_RUN_ID>" })
2334
+ * // cancel a set of workflow runs
2335
+ * await client.cancel({ ids: [
2336
+ * "<WORKFLOW_RUN_ID_1>",
2337
+ * "<WORKFLOW_RUN_ID_2>",
2338
+ * ]})
2339
+ * ```
2340
+ *
2341
+ * ### Cancel workflows starting with a url
2342
+ *
2343
+ * If you have an endpoint called `https://your-endpoint.com` and you
2344
+ * want to cancel all workflow runs on it, you can use `urlStartingWith`.
2345
+ *
2346
+ * Note that this will cancel workflows in all endpoints under
2347
+ * `https://your-endpoint.com`.
2348
+ *
2349
+ * ```ts
2350
+ * await client.cancel({ urlStartingWith: "https://your-endpoint.com" })
2167
2351
  * ```
2168
2352
  *
2169
- * @param workflowRunId run id of the workflow to delete
2353
+ * ### Cancel *all* workflows
2354
+ *
2355
+ * To cancel all pending and currently running workflows, you can
2356
+ * do it like this:
2357
+ *
2358
+ * ```ts
2359
+ * await client.cancel({ all: true })
2360
+ * ```
2361
+ *
2362
+ * @param ids run id of the workflow to delete
2363
+ * @param urlStartingWith cancel workflows starting with this url. Will be ignored
2364
+ * if `ids` parameter is set.
2365
+ * @param all set to true in order to cancel all workflows. Will be ignored
2366
+ * if `ids` or `urlStartingWith` parameters are set.
2170
2367
  * @returns true if workflow is succesfully deleted. Otherwise throws QStashError
2171
2368
  */
2172
- async cancel({ workflowRunId }) {
2369
+ async cancel({
2370
+ ids,
2371
+ urlStartingWith,
2372
+ all
2373
+ }) {
2374
+ let body;
2375
+ if (ids) {
2376
+ const runIdArray = typeof ids === "string" ? [ids] : ids;
2377
+ body = JSON.stringify({ workflowRunIds: runIdArray });
2378
+ } else if (urlStartingWith) {
2379
+ body = JSON.stringify({ workflowUrl: urlStartingWith });
2380
+ } else if (all) {
2381
+ body = "{}";
2382
+ } else {
2383
+ throw new TypeError("The `cancel` method cannot be called without any options.");
2384
+ }
2173
2385
  const result = await this.client.http.request({
2174
- path: ["v2", "workflows", "runs", `${workflowRunId}?cancel=true`],
2386
+ path: ["v2", "workflows", "runs"],
2175
2387
  method: "DELETE",
2176
- parseResponseAsJson: false
2388
+ body,
2389
+ headers: {
2390
+ "Content-Type": "application/json"
2391
+ }
2177
2392
  });
2178
- return result ?? true;
2393
+ return result;
2179
2394
  }
2180
2395
  /**
2181
2396
  * Notify a workflow run waiting for an event
@@ -2220,13 +2435,13 @@ var Client3 = class {
2220
2435
  * Trigger new workflow run and returns the workflow run id
2221
2436
  *
2222
2437
  * ```ts
2223
- * const { workflowRunId } await client.trigger({
2438
+ * const { workflowRunId } = await client.trigger({
2224
2439
  * url: "https://workflow-endpoint.com",
2225
- * body: "hello there!", // optional body
2226
- * headers: { ... }, // optional headers
2227
- * workflowRunId: "my-workflow", // optional workflow run id
2228
- * retries: 3 // optional retries in the initial request
2229
- * })
2440
+ * body: "hello there!", // Optional body
2441
+ * headers: { ... }, // Optional headers
2442
+ * workflowRunId: "my-workflow", // Optional workflow run ID
2443
+ * retries: 3 // Optional retries for the initial request
2444
+ * });
2230
2445
  *
2231
2446
  * console.log(workflowRunId)
2232
2447
  * // wfr_my-workflow
@@ -2272,10 +2487,10 @@ var Client3 = class {
2272
2487
  // Annotate the CommonJS export names for ESM import in node:
2273
2488
  0 && (module.exports = {
2274
2489
  Client,
2275
- QStashWorkflowAbort,
2276
- QStashWorkflowError,
2277
2490
  StepTypes,
2491
+ WorkflowAbort,
2278
2492
  WorkflowContext,
2493
+ WorkflowError,
2279
2494
  WorkflowLogger,
2280
2495
  serve
2281
2496
  });