@upstash/workflow 0.2.16 → 0.2.18

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
@@ -99,12 +99,13 @@ var WORKFLOW_URL_HEADER = "Upstash-Workflow-Url";
99
99
  var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
100
100
  var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
101
101
  var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
102
+ var WORKFLOW_LABEL_HEADER = "Upstash-Label";
102
103
  var WORKFLOW_PROTOCOL_VERSION = "1";
103
104
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
104
105
  var DEFAULT_CONTENT_TYPE = "application/json";
105
106
  var NO_CONCURRENCY = 1;
106
107
  var DEFAULT_RETRIES = 3;
107
- var VERSION = "v0.2.15";
108
+ var VERSION = "v0.2.18";
108
109
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
109
110
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
110
111
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -634,6 +635,7 @@ var triggerFirstInvocation = async (params) => {
634
635
  workflowUrl: workflowContext.url,
635
636
  failureUrl: workflowContext.failureUrl,
636
637
  retries: workflowContext.retries,
638
+ retryDelay: workflowContext.retryDelay,
637
639
  telemetry,
638
640
  flowControl: workflowContext.flowControl,
639
641
  useJSONContent: useJSONContent ?? false
@@ -647,6 +649,9 @@ var triggerFirstInvocation = async (params) => {
647
649
  if (useJSONContent) {
648
650
  headers["content-type"] = "application/json";
649
651
  }
652
+ if (workflowContext.label) {
653
+ headers[WORKFLOW_LABEL_HEADER] = workflowContext.label;
654
+ }
650
655
  const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
651
656
  return {
652
657
  headers,
@@ -747,10 +752,11 @@ var recreateUserHeaders = (headers) => {
747
752
  const pairs = headers.entries();
748
753
  for (const [header, value] of pairs) {
749
754
  const headerLowerCase = header.toLowerCase();
750
- if (!headerLowerCase.startsWith("upstash-workflow-") && // https://vercel.com/docs/edge-network/headers/request-headers#x-vercel-id
755
+ const isUserHeader = !headerLowerCase.startsWith("upstash-workflow-") && // https://vercel.com/docs/edge-network/headers/request-headers#x-vercel-id
751
756
  !headerLowerCase.startsWith("x-vercel-") && !headerLowerCase.startsWith("x-forwarded-") && // https://blog.cloudflare.com/preventing-request-loops-using-cdn-loop/
752
757
  headerLowerCase !== "cf-connecting-ip" && headerLowerCase !== "cdn-loop" && headerLowerCase !== "cf-ew-via" && headerLowerCase !== "cf-ray" && // For Render https://render.com
753
- headerLowerCase !== "render-proxy-ttl") {
758
+ headerLowerCase !== "render-proxy-ttl" || headerLowerCase === WORKFLOW_LABEL_HEADER.toLocaleLowerCase();
759
+ if (isUserHeader) {
754
760
  filteredHeaders.append(header, value);
755
761
  }
756
762
  }
@@ -763,6 +769,7 @@ var handleThirdPartyCallResult = async ({
763
769
  workflowUrl,
764
770
  failureUrl,
765
771
  retries,
772
+ retryDelay,
766
773
  telemetry,
767
774
  flowControl,
768
775
  debug
@@ -833,6 +840,7 @@ ${atob(callbackMessage.body ?? "")}`
833
840
  workflowUrl,
834
841
  failureUrl,
835
842
  retries,
843
+ retryDelay,
836
844
  telemetry,
837
845
  flowControl
838
846
  },
@@ -983,7 +991,8 @@ var BaseLazyStep = class _BaseLazyStep {
983
991
  workflowRunId: context.workflowRunId,
984
992
  workflowUrl: context.url,
985
993
  failureUrl: context.failureUrl,
986
- retries: context.retries,
994
+ retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
995
+ retryDelay: context.retryDelay,
987
996
  useJSONContent: false,
988
997
  telemetry,
989
998
  flowControl: context.flowControl
@@ -1002,6 +1011,9 @@ var BaseLazyStep = class _BaseLazyStep {
1002
1011
  body,
1003
1012
  headers,
1004
1013
  method: "POST",
1014
+ retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
1015
+ retryDelay: context.retryDelay,
1016
+ flowControl: context.flowControl,
1005
1017
  url: context.url
1006
1018
  }
1007
1019
  ]);
@@ -1072,6 +1084,9 @@ var LazySleepStep = class extends BaseLazyStep {
1072
1084
  headers,
1073
1085
  method: "POST",
1074
1086
  url: context.url,
1087
+ retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
1088
+ retryDelay: context.retryDelay,
1089
+ flowControl: context.flowControl,
1075
1090
  delay: isParallel ? void 0 : this.sleep
1076
1091
  }
1077
1092
  ]);
@@ -1114,6 +1129,9 @@ var LazySleepUntilStep = class extends BaseLazyStep {
1114
1129
  headers,
1115
1130
  method: "POST",
1116
1131
  url: context.url,
1132
+ retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
1133
+ retryDelay: context.retryDelay,
1134
+ flowControl: context.flowControl,
1117
1135
  notBefore: isParallel ? void 0 : this.sleepUntil
1118
1136
  }
1119
1137
  ]);
@@ -1125,17 +1143,19 @@ var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
1125
1143
  body;
1126
1144
  headers;
1127
1145
  retries;
1146
+ retryDelay;
1128
1147
  timeout;
1129
1148
  flowControl;
1130
1149
  stepType = "Call";
1131
1150
  allowUndefinedOut = false;
1132
- constructor(stepName, url, method, body, headers, retries, timeout, flowControl) {
1151
+ constructor(stepName, url, method, body, headers, retries, retryDelay, timeout, flowControl) {
1133
1152
  super(stepName);
1134
1153
  this.url = url;
1135
1154
  this.method = method;
1136
1155
  this.body = body;
1137
1156
  this.headers = headers;
1138
1157
  this.retries = retries;
1158
+ this.retryDelay = retryDelay;
1139
1159
  this.timeout = timeout;
1140
1160
  this.flowControl = flowControl;
1141
1161
  }
@@ -1210,6 +1230,9 @@ var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
1210
1230
  getHeaders({ context, telemetry, invokeCount, step }) {
1211
1231
  const { headers, contentType } = super.getHeaders({ context, telemetry, invokeCount, step });
1212
1232
  headers["Upstash-Retries"] = this.retries.toString();
1233
+ if (this.retryDelay) {
1234
+ headers["Upstash-Retry-Delay"] = this.retryDelay;
1235
+ }
1213
1236
  headers[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete,InitialBody";
1214
1237
  if (this.flowControl) {
1215
1238
  const { flowControlKey, flowControlValue } = prepareFlowControl(this.flowControl);
@@ -1231,7 +1254,7 @@ var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
1231
1254
  "Upstash-Callback-Workflow-CallType": "fromCallback",
1232
1255
  "Upstash-Callback-Workflow-Init": "false",
1233
1256
  "Upstash-Callback-Workflow-Url": context.url,
1234
- "Upstash-Callback-Feature-Set": "LazyFetch,InitialBody",
1257
+ "Upstash-Callback-Feature-Set": "LazyFetch,InitialBody,WF_DetectTrigger",
1235
1258
  "Upstash-Callback-Forward-Upstash-Workflow-Callback": "true",
1236
1259
  "Upstash-Callback-Forward-Upstash-Workflow-StepId": step.stepId.toString(),
1237
1260
  "Upstash-Callback-Forward-Upstash-Workflow-StepName": this.stepName,
@@ -1249,7 +1272,10 @@ var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
1249
1272
  headers,
1250
1273
  body: JSON.stringify(this.body),
1251
1274
  method: this.method,
1252
- url: this.url
1275
+ url: this.url,
1276
+ retries: DEFAULT_RETRIES === this.retries ? void 0 : this.retries,
1277
+ retryDelay: this.retryDelay,
1278
+ flowControl: this.flowControl
1253
1279
  }
1254
1280
  ]);
1255
1281
  }
@@ -1378,6 +1404,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
1378
1404
  headers = {},
1379
1405
  workflowRunId,
1380
1406
  retries,
1407
+ retryDelay,
1381
1408
  flowControl
1382
1409
  }) {
1383
1410
  super(stepName);
@@ -1387,6 +1414,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
1387
1414
  headers,
1388
1415
  workflowRunId: getWorkflowRunId(workflowRunId),
1389
1416
  retries,
1417
+ retryDelay,
1390
1418
  flowControl
1391
1419
  };
1392
1420
  const { workflowId } = workflow;
@@ -1431,6 +1459,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
1431
1459
  workflowUrl: context.url,
1432
1460
  failureUrl: context.failureUrl,
1433
1461
  retries: context.retries,
1462
+ retryDelay: context.retryDelay,
1434
1463
  telemetry,
1435
1464
  flowControl: context.flowControl,
1436
1465
  useJSONContent: false
@@ -1456,11 +1485,13 @@ var LazyInvokeStep = class extends BaseLazyStep {
1456
1485
  headers = {},
1457
1486
  workflowRunId = getWorkflowRunId(),
1458
1487
  retries,
1488
+ retryDelay,
1459
1489
  flowControl
1460
1490
  } = this.params;
1461
1491
  const newUrl = context.url.replace(/[^/]+$/, this.workflowId);
1462
1492
  const {
1463
1493
  retries: workflowRetries,
1494
+ retryDelay: workflowRetryDelay,
1464
1495
  failureFunction,
1465
1496
  failureUrl,
1466
1497
  useJSONContent,
@@ -1472,6 +1503,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
1472
1503
  workflowRunId,
1473
1504
  workflowUrl: newUrl,
1474
1505
  retries: retries ?? workflowRetries,
1506
+ retryDelay: retryDelay ?? workflowRetryDelay,
1475
1507
  telemetry,
1476
1508
  failureUrl: failureFunction ? newUrl : failureUrl,
1477
1509
  flowControl: flowControl ?? workflowFlowControl,
@@ -1538,6 +1570,7 @@ var WorkflowHeaders = class {
1538
1570
  getHeaders() {
1539
1571
  this.addBaseHeaders();
1540
1572
  this.addRetries();
1573
+ this.addRetryDelay();
1541
1574
  this.addFlowControl();
1542
1575
  this.addUserHeaders();
1543
1576
  this.addInvokeCount();
@@ -1551,7 +1584,7 @@ var WorkflowHeaders = class {
1551
1584
  [WORKFLOW_INIT_HEADER]: this.initHeaderValue,
1552
1585
  [WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
1553
1586
  [WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
1554
- [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody",
1587
+ [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody,WF_DetectTrigger",
1555
1588
  [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1556
1589
  ...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {},
1557
1590
  ...this.workflowConfig.telemetry && this.stepInfo?.lazyStep instanceof LazyCallStep && this.stepInfo.lazyStep.headers[AGENT_NAME_HEADER] ? { [TELEMETRY_HEADER_AGENT]: "true" } : {}
@@ -1583,6 +1616,16 @@ var WorkflowHeaders = class {
1583
1616
  this.headers.failureHeaders["Retries"] = retries;
1584
1617
  }
1585
1618
  }
1619
+ addRetryDelay() {
1620
+ if (this.workflowConfig.retryDelay === void 0 || this.workflowConfig.retryDelay === "") {
1621
+ return;
1622
+ }
1623
+ const retryDelay = this.workflowConfig.retryDelay.toString();
1624
+ this.headers.workflowHeaders["Retry-Delay"] = retryDelay;
1625
+ if (this.workflowConfig.failureUrl) {
1626
+ this.headers.failureHeaders["Retry-Delay"] = retryDelay;
1627
+ }
1628
+ }
1586
1629
  addFlowControl() {
1587
1630
  if (!this.workflowConfig.flowControl) {
1588
1631
  return;
@@ -1617,10 +1660,13 @@ var WorkflowHeaders = class {
1617
1660
  this.headers.failureHeaders["Workflow-Init"] = "false";
1618
1661
  this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
1619
1662
  this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
1620
- this.headers.failureHeaders["Feature-Set"] = "LazyFetch,InitialBody";
1663
+ this.headers.failureHeaders["Feature-Set"] = "LazyFetch,InitialBody,WF_DetectTrigger";
1621
1664
  if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
1622
1665
  this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
1623
1666
  }
1667
+ if (this.workflowConfig.retryDelay !== void 0 && this.workflowConfig.retryDelay !== "") {
1668
+ this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
1669
+ }
1624
1670
  }
1625
1671
  addContentType() {
1626
1672
  if (this.workflowConfig.useJSONContent) {
@@ -1702,6 +1748,7 @@ var submitParallelSteps = async ({
1702
1748
  workflowUrl: context.url,
1703
1749
  failureUrl: context.failureUrl,
1704
1750
  retries: context.retries,
1751
+ retryDelay: context.retryDelay,
1705
1752
  flowControl: context.flowControl,
1706
1753
  telemetry
1707
1754
  },
@@ -2122,7 +2169,7 @@ var BaseWorkflowApi = class {
2122
2169
  */
2123
2170
  async callApi(stepName, settings) {
2124
2171
  const { url, appendHeaders, method } = getProviderInfo(settings.api);
2125
- const { method: userMethod, body, headers = {}, retries = 0, timeout } = settings;
2172
+ const { method: userMethod, body, headers = {}, retries = 0, retryDelay, timeout } = settings;
2126
2173
  return await this.context.call(stepName, {
2127
2174
  url,
2128
2175
  method: userMethod ?? method,
@@ -2132,6 +2179,7 @@ var BaseWorkflowApi = class {
2132
2179
  ...headers
2133
2180
  },
2134
2181
  retries,
2182
+ retryDelay,
2135
2183
  timeout
2136
2184
  });
2137
2185
  }
@@ -2221,6 +2269,7 @@ var fetchWithContextCall = async (context, agentCallParams, ...params) => {
2221
2269
  body,
2222
2270
  timeout: agentCallParams?.timeout,
2223
2271
  retries: agentCallParams?.retries,
2272
+ retryDelay: agentCallParams?.retryDelay,
2224
2273
  flowControl: agentCallParams?.flowControl
2225
2274
  });
2226
2275
  const responseHeaders = new Headers(
@@ -2665,11 +2714,58 @@ var WorkflowContext = class {
2665
2714
  * Number of retries
2666
2715
  */
2667
2716
  retries;
2717
+ /**
2718
+ * Delay between retries.
2719
+ *
2720
+ * By default, the `retryDelay` is exponential backoff.
2721
+ * More details can be found in: https://upstash.com/docs/qstash/features/retry.
2722
+ *
2723
+ * The `retryDelay` option allows you to customize the delay (in milliseconds) between retry attempts when message delivery fails.
2724
+ *
2725
+ * You can use mathematical expressions and the following built-in functions to calculate the delay dynamically.
2726
+ * The special variable `retried` represents the current retry attempt count (starting from 0).
2727
+ *
2728
+ * Supported functions:
2729
+ * - `pow`
2730
+ * - `sqrt`
2731
+ * - `abs`
2732
+ * - `exp`
2733
+ * - `floor`
2734
+ * - `ceil`
2735
+ * - `round`
2736
+ * - `min`
2737
+ * - `max`
2738
+ *
2739
+ * Examples of valid `retryDelay` values:
2740
+ * ```ts
2741
+ * 1000 // 1 second
2742
+ * 1000 * (1 + retried) // 1 second multiplied by the current retry attempt
2743
+ * pow(2, retried) // 2 to the power of the current retry attempt
2744
+ * max(10, pow(2, retried)) // The greater of 10 or 2^retried
2745
+ * ```
2746
+ */
2747
+ retryDelay;
2668
2748
  /**
2669
2749
  * Settings for controlling the number of active requests
2670
2750
  * and number of requests per second with the same key.
2671
2751
  */
2672
2752
  flowControl;
2753
+ /**
2754
+ * Label to apply to the workflow run.
2755
+ *
2756
+ * Can be used to filter the workflow run logs.
2757
+ *
2758
+ * Can be set by passing a `label` parameter when triggering the workflow
2759
+ * with `client.trigger`:
2760
+ *
2761
+ * ```ts
2762
+ * await client.trigger({
2763
+ * url: "https://workflow-endpoint.com",
2764
+ * label: "my-label"
2765
+ * });
2766
+ * ```
2767
+ */
2768
+ label;
2673
2769
  constructor({
2674
2770
  qstashClient,
2675
2771
  workflowRunId,
@@ -2681,9 +2777,11 @@ var WorkflowContext = class {
2681
2777
  initialPayload,
2682
2778
  env,
2683
2779
  retries,
2780
+ retryDelay,
2684
2781
  telemetry,
2685
2782
  invokeCount,
2686
- flowControl
2783
+ flowControl,
2784
+ label
2687
2785
  }) {
2688
2786
  this.qstashClient = qstashClient;
2689
2787
  this.workflowRunId = workflowRunId;
@@ -2694,7 +2792,9 @@ var WorkflowContext = class {
2694
2792
  this.requestPayload = initialPayload;
2695
2793
  this.env = env ?? {};
2696
2794
  this.retries = retries ?? DEFAULT_RETRIES;
2795
+ this.retryDelay = retryDelay;
2697
2796
  this.flowControl = flowControl;
2797
+ this.label = label;
2698
2798
  this.executor = new AutoExecutor(this, this.steps, telemetry, invokeCount, debug);
2699
2799
  }
2700
2800
  /**
@@ -2775,6 +2875,7 @@ var WorkflowContext = class {
2775
2875
  settings.body,
2776
2876
  settings.headers || {},
2777
2877
  settings.retries || 0,
2878
+ settings.retryDelay,
2778
2879
  settings.timeout,
2779
2880
  settings.flowControl ?? settings.workflow.options.flowControl
2780
2881
  );
@@ -2785,6 +2886,7 @@ var WorkflowContext = class {
2785
2886
  body,
2786
2887
  headers = {},
2787
2888
  retries = 0,
2889
+ retryDelay,
2788
2890
  timeout,
2789
2891
  flowControl
2790
2892
  } = settings;
@@ -2795,6 +2897,7 @@ var WorkflowContext = class {
2795
2897
  body,
2796
2898
  headers,
2797
2899
  retries,
2900
+ retryDelay,
2798
2901
  timeout,
2799
2902
  flowControl
2800
2903
  );
@@ -2805,7 +2908,7 @@ var WorkflowContext = class {
2805
2908
  * Pauses workflow execution until a specific event occurs or a timeout is reached.
2806
2909
  *
2807
2910
  *```ts
2808
- * const result = await workflow.waitForEvent("payment-confirmed", {
2911
+ * const result = await workflow.waitForEvent("payment-confirmed", "payment.confirmed", {
2809
2912
  * timeout: "5m"
2810
2913
  * });
2811
2914
  *```
@@ -2831,7 +2934,7 @@ var WorkflowContext = class {
2831
2934
  * @param stepName
2832
2935
  * @param eventId - Unique identifier for the event to wait for
2833
2936
  * @param options - Configuration options.
2834
- * @returns `{ timeout: boolean, eventData: unknown }`.
2937
+ * @returns `{ timeout: boolean, eventData: TEventData }`.
2835
2938
  * The `timeout` property specifies if the workflow has timed out. The `eventData`
2836
2939
  * is the data passed when notifying this workflow of an event.
2837
2940
  */
@@ -2991,7 +3094,9 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
2991
3094
  initialPayload: context.requestPayload,
2992
3095
  env: context.env,
2993
3096
  retries: context.retries,
2994
- flowControl: context.flowControl
3097
+ retryDelay: context.retryDelay,
3098
+ flowControl: context.flowControl,
3099
+ label: context.label
2995
3100
  });
2996
3101
  try {
2997
3102
  await routeFunction(disabledContext);
@@ -2999,6 +3104,9 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
2999
3104
  if (error instanceof WorkflowAbort && error.stepName === this.disabledMessage || error instanceof WorkflowNonRetryableError) {
3000
3105
  return ok("step-found");
3001
3106
  }
3107
+ console.warn(
3108
+ "Upstash Workflow: Received an error while authorizing request. Please avoid throwing errors before the first step of your workflow."
3109
+ );
3002
3110
  return err(error);
3003
3111
  }
3004
3112
  return ok("run-ended");
@@ -3140,9 +3248,9 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
3140
3248
  };
3141
3249
  }
3142
3250
  };
3143
- var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, flowControl, debug) => {
3251
+ var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, retryDelay, flowControl, debug) => {
3144
3252
  if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
3145
- return ok("not-failure-callback");
3253
+ return ok({ result: "not-failure-callback" });
3146
3254
  }
3147
3255
  if (!failureFunction) {
3148
3256
  return err(
@@ -3165,20 +3273,23 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
3165
3273
  if (!errorMessage) {
3166
3274
  errorMessage = `Couldn't parse 'failResponse' in 'failureFunction', received: '${decodedBody}'`;
3167
3275
  }
3276
+ const userHeaders = recreateUserHeaders(request.headers);
3168
3277
  const workflowContext = new WorkflowContext({
3169
3278
  qstashClient,
3170
3279
  workflowRunId,
3171
3280
  initialPayload: sourceBody ? initialPayloadParser(decodeBase64(sourceBody)) : void 0,
3172
- headers: recreateUserHeaders(request.headers),
3281
+ headers: userHeaders,
3173
3282
  steps: [],
3174
3283
  url,
3175
3284
  failureUrl: url,
3176
3285
  debug,
3177
3286
  env,
3178
3287
  retries,
3288
+ retryDelay,
3179
3289
  flowControl,
3180
- telemetry: void 0
3290
+ telemetry: void 0,
3181
3291
  // not going to make requests in authentication check
3292
+ label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0
3182
3293
  });
3183
3294
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
3184
3295
  routeFunction,
@@ -3190,16 +3301,16 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
3190
3301
  } else if (authCheck.value === "run-ended") {
3191
3302
  return err(new WorkflowError("Not authorized to run the failure function."));
3192
3303
  }
3193
- await failureFunction({
3304
+ const failureResponse = await failureFunction({
3194
3305
  context: workflowContext,
3195
3306
  failStatus: status,
3196
3307
  failResponse: errorMessage,
3197
3308
  failHeaders: header
3198
3309
  });
3310
+ return ok({ result: "is-failure-callback", response: failureResponse });
3199
3311
  } catch (error) {
3200
3312
  return err(error);
3201
3313
  }
3202
- return ok("is-failure-callback");
3203
3314
  };
3204
3315
 
3205
3316
  // src/serve/options.ts
@@ -3215,8 +3326,8 @@ var processOptions = (options) => {
3215
3326
  baseUrl: environment.QSTASH_URL,
3216
3327
  token: environment.QSTASH_TOKEN
3217
3328
  }),
3218
- onStepFinish: (workflowRunId, finishCondition) => {
3219
- if (finishCondition === "auth-fail") {
3329
+ onStepFinish: (workflowRunId, _finishCondition, detailedFinishCondition) => {
3330
+ if (detailedFinishCondition?.condition === "auth-fail") {
3220
3331
  console.error(AUTH_FAIL_MESSAGE);
3221
3332
  return new Response(
3222
3333
  JSON.stringify({
@@ -3224,19 +3335,33 @@ var processOptions = (options) => {
3224
3335
  workflowRunId
3225
3336
  }),
3226
3337
  {
3227
- status: 400
3338
+ status: 400,
3339
+ headers: {
3340
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3341
+ }
3228
3342
  }
3229
3343
  );
3230
- } else if (finishCondition instanceof WorkflowNonRetryableError) {
3231
- return new Response(JSON.stringify(formatWorkflowError(finishCondition)), {
3344
+ } else if (detailedFinishCondition?.condition === "non-retryable-error") {
3345
+ return new Response(JSON.stringify(formatWorkflowError(detailedFinishCondition.result)), {
3232
3346
  headers: {
3233
- "Upstash-NonRetryable-Error": "true"
3347
+ "Upstash-NonRetryable-Error": "true",
3348
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3234
3349
  },
3235
3350
  status: 489
3236
3351
  });
3352
+ } else if (detailedFinishCondition?.condition === "failure-callback") {
3353
+ return new Response(detailedFinishCondition.result ?? void 0, {
3354
+ status: 200,
3355
+ headers: {
3356
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3357
+ }
3358
+ });
3237
3359
  }
3238
3360
  return new Response(JSON.stringify({ workflowRunId }), {
3239
- status: 200
3361
+ status: 200,
3362
+ headers: {
3363
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3364
+ }
3240
3365
  });
3241
3366
  },
3242
3367
  initialPayloadParser: (initialRequest) => {
@@ -3310,6 +3435,7 @@ var serveBase = (routeFunction, telemetry, options) => {
3310
3435
  baseUrl,
3311
3436
  env,
3312
3437
  retries,
3438
+ retryDelay,
3313
3439
  useJSONContent,
3314
3440
  disableTelemetry,
3315
3441
  flowControl,
@@ -3340,10 +3466,14 @@ var serveBase = (routeFunction, telemetry, options) => {
3340
3466
  debug
3341
3467
  );
3342
3468
  if (workflowRunEnded) {
3343
- return onStepFinish(workflowRunId, "workflow-already-ended");
3469
+ return onStepFinish(workflowRunId, "workflow-already-ended", {
3470
+ condition: "workflow-already-ended"
3471
+ });
3344
3472
  }
3345
3473
  if (isLastDuplicate) {
3346
- return onStepFinish(workflowRunId, "duplicate-step");
3474
+ return onStepFinish(workflowRunId, "duplicate-step", {
3475
+ condition: "duplicate-step"
3476
+ });
3347
3477
  }
3348
3478
  const failureCheck = await handleFailure(
3349
3479
  request,
@@ -3354,16 +3484,21 @@ var serveBase = (routeFunction, telemetry, options) => {
3354
3484
  failureFunction,
3355
3485
  env,
3356
3486
  retries,
3487
+ retryDelay,
3357
3488
  flowControl,
3358
3489
  debug
3359
3490
  );
3360
3491
  if (failureCheck.isErr()) {
3361
3492
  throw failureCheck.error;
3362
- } else if (failureCheck.value === "is-failure-callback") {
3493
+ } else if (failureCheck.value.result === "is-failure-callback") {
3363
3494
  await debug?.log("WARN", "RESPONSE_DEFAULT", "failureFunction executed");
3364
- return onStepFinish(workflowRunId, "failure-callback");
3495
+ return onStepFinish(workflowRunId, "failure-callback", {
3496
+ condition: "failure-callback",
3497
+ result: failureCheck.value.response
3498
+ });
3365
3499
  }
3366
3500
  const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
3501
+ const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
3367
3502
  const workflowContext = new WorkflowContext({
3368
3503
  qstashClient,
3369
3504
  workflowRunId,
@@ -3375,9 +3510,11 @@ var serveBase = (routeFunction, telemetry, options) => {
3375
3510
  debug,
3376
3511
  env,
3377
3512
  retries,
3513
+ retryDelay,
3378
3514
  telemetry,
3379
3515
  invokeCount,
3380
- flowControl
3516
+ flowControl,
3517
+ label
3381
3518
  });
3382
3519
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
3383
3520
  routeFunction,
@@ -3390,7 +3527,8 @@ var serveBase = (routeFunction, telemetry, options) => {
3390
3527
  await debug?.log("ERROR", "ERROR", { error: AUTH_FAIL_MESSAGE });
3391
3528
  return onStepFinish(
3392
3529
  isFirstInvocation ? "no-workflow-id" : workflowContext.workflowRunId,
3393
- "auth-fail"
3530
+ "auth-fail",
3531
+ { condition: "auth-fail" }
3394
3532
  );
3395
3533
  }
3396
3534
  const callReturnCheck = await handleThirdPartyCallResult({
@@ -3400,6 +3538,7 @@ var serveBase = (routeFunction, telemetry, options) => {
3400
3538
  workflowUrl,
3401
3539
  failureUrl: workflowFailureUrl,
3402
3540
  retries,
3541
+ retryDelay,
3403
3542
  flowControl,
3404
3543
  telemetry,
3405
3544
  debug
@@ -3427,19 +3566,28 @@ var serveBase = (routeFunction, telemetry, options) => {
3427
3566
  debug
3428
3567
  });
3429
3568
  if (result.isOk() && result.value instanceof WorkflowNonRetryableError) {
3430
- return onStepFinish(workflowRunId, result.value);
3569
+ return onStepFinish(workflowRunId, result.value, {
3570
+ condition: "non-retryable-error",
3571
+ result: result.value
3572
+ });
3431
3573
  }
3432
3574
  if (result.isErr()) {
3433
3575
  await debug?.log("ERROR", "ERROR", { error: result.error.message });
3434
3576
  throw result.error;
3435
3577
  }
3436
3578
  await debug?.log("INFO", "RESPONSE_WORKFLOW");
3437
- return onStepFinish(workflowContext.workflowRunId, "success");
3579
+ return onStepFinish(workflowContext.workflowRunId, "success", {
3580
+ condition: "success"
3581
+ });
3438
3582
  } else if (callReturnCheck.value === "workflow-ended") {
3439
- return onStepFinish(workflowContext.workflowRunId, "workflow-already-ended");
3583
+ return onStepFinish(workflowContext.workflowRunId, "workflow-already-ended", {
3584
+ condition: "workflow-already-ended"
3585
+ });
3440
3586
  }
3441
3587
  await debug?.log("INFO", "RESPONSE_DEFAULT");
3442
- return onStepFinish("no-workflow-id", "fromCallback");
3588
+ return onStepFinish("no-workflow-id", "fromCallback", {
3589
+ condition: "fromCallback"
3590
+ });
3443
3591
  };
3444
3592
  const safeHandler = async (request) => {
3445
3593
  try {
@@ -3454,11 +3602,17 @@ var serveBase = (routeFunction, telemetry, options) => {
3454
3602
  Original error: '${formattedError.message}'`;
3455
3603
  console.error(errorMessage);
3456
3604
  return new Response(errorMessage, {
3457
- status: 500
3605
+ status: 500,
3606
+ headers: {
3607
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3608
+ }
3458
3609
  });
3459
3610
  }
3460
3611
  return new Response(JSON.stringify(formattedError), {
3461
- status: 500
3612
+ status: 500,
3613
+ headers: {
3614
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3615
+ }
3462
3616
  });
3463
3617
  }
3464
3618
  };
@@ -3533,6 +3687,20 @@ var DLQ = class _DLQ {
3533
3687
  }
3534
3688
  return workflowRuns[0];
3535
3689
  }
3690
+ /**
3691
+ * Retry the failure callback of a workflow run whose failureUrl/failureFunction
3692
+ * request has failed.
3693
+ *
3694
+ * @param dlqId - The ID of the DLQ message to retry.
3695
+ * @returns
3696
+ */
3697
+ async retryFailureFunction({ dlqId }) {
3698
+ const response = await this.client.http.request({
3699
+ path: ["v2", "workflows", "dlq", "callback", dlqId],
3700
+ method: "POST"
3701
+ });
3702
+ return response;
3703
+ }
3536
3704
  static handleDLQOptions(options) {
3537
3705
  const { dlqId, flowControl, retries } = options;
3538
3706
  const headers = {};
@@ -3691,16 +3859,21 @@ var Client4 = class {
3691
3859
  const finalWorkflowRunId = getWorkflowRunId(option.workflowRunId);
3692
3860
  const context = new WorkflowContext({
3693
3861
  qstashClient: this.client,
3694
- // @ts-expect-error headers type mismatch
3695
- headers: new Headers(option.headers ?? {}),
3862
+ // @ts-expect-error header type mismatch because of bun
3863
+ headers: new Headers({
3864
+ ...option.headers ?? {},
3865
+ ...option.label ? { [WORKFLOW_LABEL_HEADER]: option.label } : {}
3866
+ }),
3696
3867
  initialPayload: option.body,
3697
3868
  steps: [],
3698
3869
  url: option.url,
3699
3870
  workflowRunId: finalWorkflowRunId,
3700
3871
  retries: option.retries,
3872
+ retryDelay: option.retryDelay,
3701
3873
  telemetry: { sdk: SDK_TELEMETRY },
3702
3874
  flowControl: option.flowControl,
3703
- failureUrl
3875
+ failureUrl,
3876
+ label: option.label
3704
3877
  });
3705
3878
  return {
3706
3879
  workflowContext: context,
@@ -3766,6 +3939,9 @@ var Client4 = class {
3766
3939
  if (workflowCreatedAt) {
3767
3940
  urlParams.append("workflowCreatedAt", workflowCreatedAt.toString());
3768
3941
  }
3942
+ if (params?.label) {
3943
+ urlParams.append("label", params.label);
3944
+ }
3769
3945
  const result = await this.client.http.request({
3770
3946
  path: ["v2", "workflows", `events?${urlParams.toString()}`]
3771
3947
  });