@upstash/workflow 1.3.0 → 1.3.2-rc

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.
Files changed (50) hide show
  1. package/astro.d.mts +2 -2
  2. package/astro.d.ts +2 -2
  3. package/astro.js +81 -14
  4. package/astro.mjs +1 -1
  5. package/{chunk-G24J5PCC.mjs → chunk-ICDNN4UW.mjs} +84 -14
  6. package/cloudflare.d.mts +2 -2
  7. package/cloudflare.d.ts +2 -2
  8. package/cloudflare.js +81 -14
  9. package/cloudflare.mjs +1 -1
  10. package/express.d.mts +2 -2
  11. package/express.d.ts +2 -2
  12. package/express.js +81 -14
  13. package/express.mjs +1 -1
  14. package/h3.d.mts +2 -2
  15. package/h3.d.ts +2 -2
  16. package/h3.js +81 -14
  17. package/h3.mjs +1 -1
  18. package/hono.d.mts +2 -2
  19. package/hono.d.ts +2 -2
  20. package/hono.js +81 -14
  21. package/hono.mjs +1 -1
  22. package/index.d.mts +43 -7
  23. package/index.d.ts +43 -7
  24. package/index.js +84 -15
  25. package/index.mjs +8 -3
  26. package/nextjs.d.mts +2 -2
  27. package/nextjs.d.ts +2 -2
  28. package/nextjs.js +81 -14
  29. package/nextjs.mjs +1 -1
  30. package/package.json +1 -1
  31. package/react-router.d.mts +2 -2
  32. package/react-router.d.ts +2 -2
  33. package/react-router.js +81 -14
  34. package/react-router.mjs +1 -1
  35. package/{serve-many-D3D9uE4u.d.mts → serve-many-CG3BFvO3.d.mts} +1 -1
  36. package/{serve-many-kPOasiyb.d.ts → serve-many-iJF1IUXk.d.ts} +1 -1
  37. package/solidjs.d.mts +1 -1
  38. package/solidjs.d.ts +1 -1
  39. package/solidjs.js +81 -14
  40. package/solidjs.mjs +1 -1
  41. package/svelte.d.mts +2 -2
  42. package/svelte.d.ts +2 -2
  43. package/svelte.js +81 -14
  44. package/svelte.mjs +1 -1
  45. package/tanstack.d.mts +2 -2
  46. package/tanstack.d.ts +2 -2
  47. package/tanstack.js +81 -14
  48. package/tanstack.mjs +1 -1
  49. package/{types-B_E1VAK6.d.ts → types-CekOpKvz.d.mts} +14 -6
  50. package/{types-B_E1VAK6.d.mts → types-CekOpKvz.d.ts} +14 -6
package/astro.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { APIContext, APIRoute } from 'astro';
2
- import { u as WorkflowContext, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-B_E1VAK6.mjs';
3
- import { s as serveManyBase } from './serve-many-D3D9uE4u.mjs';
2
+ import { u as WorkflowContext, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-CekOpKvz.mjs';
3
+ import { s as serveManyBase } from './serve-many-CG3BFvO3.mjs';
4
4
  import '@upstash/qstash';
5
5
  import 'zod';
6
6
 
package/astro.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { APIContext, APIRoute } from 'astro';
2
- import { u as WorkflowContext, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-B_E1VAK6.js';
3
- import { s as serveManyBase } from './serve-many-kPOasiyb.js';
2
+ import { u as WorkflowContext, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-CekOpKvz.js';
3
+ import { s as serveManyBase } from './serve-many-iJF1IUXk.js';
4
4
  import '@upstash/qstash';
5
5
  import 'zod';
6
6
 
package/astro.js CHANGED
@@ -103,6 +103,22 @@ var formatWorkflowError = (error) => {
103
103
  message: `An error occured while executing workflow: '${typeof error === "string" ? error : JSON.stringify(error)}'`
104
104
  };
105
105
  };
106
+ var attachStepNameToError = (error, stepName) => {
107
+ if (typeof error !== "object" || error === null) return;
108
+ if ("errorStepName" in error) return;
109
+ if (!Object.isExtensible(error)) return;
110
+ try {
111
+ error.errorStepName = stepName;
112
+ } catch {
113
+ }
114
+ };
115
+ var getStepNameFromError = (error) => {
116
+ if (typeof error === "object" && error !== null) {
117
+ const stepName = error.errorStepName;
118
+ return typeof stepName === "string" ? stepName : void 0;
119
+ }
120
+ return void 0;
121
+ };
106
122
  function getConstructorName(obj) {
107
123
  if (obj === null || obj === void 0) {
108
124
  return null;
@@ -196,12 +212,13 @@ var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
196
212
  var WORKFLOW_LABEL_HEADER = "Upstash-Label";
197
213
  var WORKFLOW_UNKOWN_SDK_VERSION_HEADER = "Upstash-Workflow-Unknown-Sdk";
198
214
  var WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER = "upstash-workflow-trigger-by-sdk";
215
+ var WORKFLOW_ERROR_STEP_NAME_HEADER = "Upstash-Error-Step-Name";
199
216
  var WORKFLOW_PROTOCOL_VERSION = "1";
200
217
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
201
218
  var DEFAULT_CONTENT_TYPE = "application/json";
202
219
  var NO_CONCURRENCY = 1;
203
220
  var DEFAULT_RETRIES = 3;
204
- var VERSION = "v1.2.1";
221
+ var VERSION = "v1.3.2";
205
222
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
206
223
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
207
224
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -216,6 +233,31 @@ var import_qstash4 = require("@upstash/qstash");
216
233
  // src/utils.ts
217
234
  var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
218
235
  var NANOID_LENGTH = 21;
236
+ var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
237
+ function validateLabel(label) {
238
+ if (label === void 0) return;
239
+ const labels = Array.isArray(label) ? label : [label];
240
+ if (labels.length === 0) {
241
+ throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
242
+ }
243
+ for (const value of labels) {
244
+ if (!RESOURCE_NAME_PATTERN.test(value)) {
245
+ throw new WorkflowNonRetryableError(
246
+ `Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
247
+ );
248
+ }
249
+ }
250
+ }
251
+ function serializeLabel(label) {
252
+ return Array.isArray(label) ? label.join(",") : label;
253
+ }
254
+ function validateFlowControl(flowControl) {
255
+ if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
256
+ throw new WorkflowNonRetryableError(
257
+ `Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
258
+ );
259
+ }
260
+ }
219
261
  function getRandomInt() {
220
262
  return Math.floor(Math.random() * NANOID_CHARS.length);
221
263
  }
@@ -751,8 +793,8 @@ var triggerFirstInvocation = async (params) => {
751
793
  if (unknownSdk) {
752
794
  headers[WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER] = "true";
753
795
  }
754
- if (workflowContext.label) {
755
- headers[WORKFLOW_LABEL_HEADER] = workflowContext.label;
796
+ if (workflowContext.labels.length > 0) {
797
+ headers[WORKFLOW_LABEL_HEADER] = serializeLabel(workflowContext.labels);
756
798
  }
757
799
  const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
758
800
  return {
@@ -1554,8 +1596,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1554
1596
  });
1555
1597
  triggerHeaders["Upstash-Workflow-Invoke"] = "true";
1556
1598
  if (label) {
1557
- triggerHeaders[WORKFLOW_LABEL_HEADER] = label;
1558
- triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = label;
1599
+ const labelHeader = serializeLabel(label);
1600
+ triggerHeaders[WORKFLOW_LABEL_HEADER] = labelHeader;
1601
+ triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = labelHeader;
1559
1602
  }
1560
1603
  return { headers: triggerHeaders, contentType };
1561
1604
  }
@@ -1898,7 +1941,13 @@ var submitSingleStep = async ({
1898
1941
  await dispatchLifecycle("beforeExecution", {
1899
1942
  stepName: lazyStep.stepName
1900
1943
  });
1901
- const resultStep = await lazyStep.getResultStep(concurrency, stepId);
1944
+ let resultStep;
1945
+ try {
1946
+ resultStep = await lazyStep.getResultStep(concurrency, stepId);
1947
+ } catch (error) {
1948
+ attachStepNameToError(error, lazyStep.stepName);
1949
+ throw error;
1950
+ }
1902
1951
  const { headers } = lazyStep.getHeaders({
1903
1952
  context,
1904
1953
  step: resultStep,
@@ -2121,9 +2170,11 @@ var AutoExecutor = class _AutoExecutor {
2121
2170
  if (isInstanceOf(error, WorkflowAbort) || isInstanceOf(error, import_qstash5.QstashError) && error.status === 400 || isInstanceOf(error, import_qstash5.QstashError) && error.status === 412) {
2122
2171
  throw error;
2123
2172
  }
2124
- throw new WorkflowError(
2173
+ const wrappedError = new WorkflowError(
2125
2174
  `Error submitting steps to QStash in partial parallel step execution: ${error}`
2126
2175
  );
2176
+ attachStepNameToError(wrappedError, parallelSteps[stepIndex].stepName);
2177
+ throw wrappedError;
2127
2178
  }
2128
2179
  break;
2129
2180
  }
@@ -2696,9 +2747,10 @@ var WorkflowContext = class {
2696
2747
  */
2697
2748
  env;
2698
2749
  /**
2699
- * Label to apply to the workflow run.
2750
+ * Labels attached to the workflow run.
2700
2751
  *
2701
- * Can be used to filter the workflow run logs.
2752
+ * Can be used to filter the workflow run logs. A run can have multiple
2753
+ * labels when triggered with `label: string[]`.
2702
2754
  *
2703
2755
  * Can be set by passing a `label` parameter when triggering the workflow
2704
2756
  * with `client.trigger`:
@@ -2706,11 +2758,20 @@ var WorkflowContext = class {
2706
2758
  * ```ts
2707
2759
  * await client.trigger({
2708
2760
  * url: "https://workflow-endpoint.com",
2709
- * label: "my-label"
2761
+ * label: ["label-1", "label-2"]
2710
2762
  * });
2711
2763
  * ```
2712
2764
  */
2713
- label;
2765
+ labels;
2766
+ /**
2767
+ * Label of the workflow run.
2768
+ *
2769
+ * @deprecated Use `labels` instead. When a run has multiple labels, this
2770
+ * only returns the first one.
2771
+ */
2772
+ get label() {
2773
+ return this.labels[0];
2774
+ }
2714
2775
  /**
2715
2776
  * Number of times QStash has retried delivering the current request.
2716
2777
  *
@@ -2741,7 +2802,7 @@ var WorkflowContext = class {
2741
2802
  this.headers = headers;
2742
2803
  this.requestPayload = initialPayload;
2743
2804
  this.env = env ?? {};
2744
- this.label = label;
2805
+ this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
2745
2806
  this.retried = retried ?? 0;
2746
2807
  const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
2747
2808
  middlewareManagerInstance.assignContext(this);
@@ -2822,8 +2883,10 @@ var WorkflowContext = class {
2822
2883
  await this.addStep(new LazySleepUntilStep(this, stepName, time));
2823
2884
  }
2824
2885
  async call(stepName, settings) {
2886
+ validateFlowControl(settings.flowControl);
2825
2887
  let callStep;
2826
2888
  if ("workflow" in settings) {
2889
+ validateLabel(settings.label);
2827
2890
  const url = getNewUrlFromWorkflowId(this.url, settings.workflow.workflowId);
2828
2891
  const stringBody = typeof settings.body === "string" ? settings.body : settings.body === void 0 ? void 0 : JSON.stringify(settings.body);
2829
2892
  callStep = new LazyCallStep({
@@ -2931,6 +2994,8 @@ var WorkflowContext = class {
2931
2994
  );
2932
2995
  }
2933
2996
  async invoke(stepName, settings) {
2997
+ validateLabel(settings.label);
2998
+ validateFlowControl(settings.flowControl);
2934
2999
  return await this.addStep(
2935
3000
  new LazyInvokeStep(this, stepName, settings)
2936
3001
  );
@@ -3016,7 +3081,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
3016
3081
  url: context.url,
3017
3082
  initialPayload: context.requestPayload,
3018
3083
  env: context.env,
3019
- label: context.label,
3084
+ label: context.labels,
3020
3085
  retried: context.retried
3021
3086
  });
3022
3087
  try {
@@ -3854,10 +3919,12 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
3854
3919
  await middlewareManager.dispatchDebug("onError", {
3855
3920
  error: isInstanceOf(error, Error) ? error : new Error(formattedError.message)
3856
3921
  });
3922
+ const stepName = getStepNameFromError(error)?.replace(/[\u0000-\u001F\u007F]+/g, " ").trim();
3857
3923
  return new Response(JSON.stringify(formattedError), {
3858
3924
  status: 500,
3859
3925
  headers: {
3860
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3926
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
3927
+ ...stepName ? { [WORKFLOW_ERROR_STEP_NAME_HEADER]: stepName } : {}
3861
3928
  }
3862
3929
  });
3863
3930
  }
package/astro.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  SDK_TELEMETRY,
3
3
  serveBase,
4
4
  serveManyBase
5
- } from "./chunk-G24J5PCC.mjs";
5
+ } from "./chunk-ICDNN4UW.mjs";
6
6
 
7
7
  // platforms/astro.ts
8
8
  var telemetry = {
@@ -72,6 +72,22 @@ var formatWorkflowError = (error) => {
72
72
  message: `An error occured while executing workflow: '${typeof error === "string" ? error : JSON.stringify(error)}'`
73
73
  };
74
74
  };
75
+ var attachStepNameToError = (error, stepName) => {
76
+ if (typeof error !== "object" || error === null) return;
77
+ if ("errorStepName" in error) return;
78
+ if (!Object.isExtensible(error)) return;
79
+ try {
80
+ error.errorStepName = stepName;
81
+ } catch {
82
+ }
83
+ };
84
+ var getStepNameFromError = (error) => {
85
+ if (typeof error === "object" && error !== null) {
86
+ const stepName = error.errorStepName;
87
+ return typeof stepName === "string" ? stepName : void 0;
88
+ }
89
+ return void 0;
90
+ };
75
91
  function getConstructorName(obj) {
76
92
  if (obj === null || obj === void 0) {
77
93
  return null;
@@ -107,12 +123,13 @@ var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
107
123
  var WORKFLOW_LABEL_HEADER = "Upstash-Label";
108
124
  var WORKFLOW_UNKOWN_SDK_VERSION_HEADER = "Upstash-Workflow-Unknown-Sdk";
109
125
  var WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER = "upstash-workflow-trigger-by-sdk";
126
+ var WORKFLOW_ERROR_STEP_NAME_HEADER = "Upstash-Error-Step-Name";
110
127
  var WORKFLOW_PROTOCOL_VERSION = "1";
111
128
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
112
129
  var DEFAULT_CONTENT_TYPE = "application/json";
113
130
  var NO_CONCURRENCY = 1;
114
131
  var DEFAULT_RETRIES = 3;
115
- var VERSION = "v1.2.1";
132
+ var VERSION = "v1.3.2";
116
133
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
117
134
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
118
135
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -342,6 +359,31 @@ function buildBulkActionQueryParameters(request, options) {
342
359
  // src/utils.ts
343
360
  var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
344
361
  var NANOID_LENGTH = 21;
362
+ var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
363
+ function validateLabel(label) {
364
+ if (label === void 0) return;
365
+ const labels = Array.isArray(label) ? label : [label];
366
+ if (labels.length === 0) {
367
+ throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
368
+ }
369
+ for (const value of labels) {
370
+ if (!RESOURCE_NAME_PATTERN.test(value)) {
371
+ throw new WorkflowNonRetryableError(
372
+ `Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
373
+ );
374
+ }
375
+ }
376
+ }
377
+ function serializeLabel(label) {
378
+ return Array.isArray(label) ? label.join(",") : label;
379
+ }
380
+ function validateFlowControl(flowControl) {
381
+ if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
382
+ throw new WorkflowNonRetryableError(
383
+ `Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
384
+ );
385
+ }
386
+ }
345
387
  function getRandomInt() {
346
388
  return Math.floor(Math.random() * NANOID_CHARS.length);
347
389
  }
@@ -863,8 +905,8 @@ var triggerFirstInvocation = async (params) => {
863
905
  if (unknownSdk) {
864
906
  headers[WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER] = "true";
865
907
  }
866
- if (workflowContext.label) {
867
- headers[WORKFLOW_LABEL_HEADER] = workflowContext.label;
908
+ if (workflowContext.labels.length > 0) {
909
+ headers[WORKFLOW_LABEL_HEADER] = serializeLabel(workflowContext.labels);
868
910
  }
869
911
  const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
870
912
  return {
@@ -1666,8 +1708,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1666
1708
  });
1667
1709
  triggerHeaders["Upstash-Workflow-Invoke"] = "true";
1668
1710
  if (label) {
1669
- triggerHeaders[WORKFLOW_LABEL_HEADER] = label;
1670
- triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = label;
1711
+ const labelHeader = serializeLabel(label);
1712
+ triggerHeaders[WORKFLOW_LABEL_HEADER] = labelHeader;
1713
+ triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = labelHeader;
1671
1714
  }
1672
1715
  return { headers: triggerHeaders, contentType };
1673
1716
  }
@@ -2010,7 +2053,13 @@ var submitSingleStep = async ({
2010
2053
  await dispatchLifecycle("beforeExecution", {
2011
2054
  stepName: lazyStep.stepName
2012
2055
  });
2013
- const resultStep = await lazyStep.getResultStep(concurrency, stepId);
2056
+ let resultStep;
2057
+ try {
2058
+ resultStep = await lazyStep.getResultStep(concurrency, stepId);
2059
+ } catch (error) {
2060
+ attachStepNameToError(error, lazyStep.stepName);
2061
+ throw error;
2062
+ }
2014
2063
  const { headers } = lazyStep.getHeaders({
2015
2064
  context,
2016
2065
  step: resultStep,
@@ -2233,9 +2282,11 @@ var AutoExecutor = class _AutoExecutor {
2233
2282
  if (isInstanceOf(error, WorkflowAbort) || isInstanceOf(error, QstashError5) && error.status === 400 || isInstanceOf(error, QstashError5) && error.status === 412) {
2234
2283
  throw error;
2235
2284
  }
2236
- throw new WorkflowError(
2285
+ const wrappedError = new WorkflowError(
2237
2286
  `Error submitting steps to QStash in partial parallel step execution: ${error}`
2238
2287
  );
2288
+ attachStepNameToError(wrappedError, parallelSteps[stepIndex].stepName);
2289
+ throw wrappedError;
2239
2290
  }
2240
2291
  break;
2241
2292
  }
@@ -2733,9 +2784,10 @@ var WorkflowContext = class {
2733
2784
  */
2734
2785
  env;
2735
2786
  /**
2736
- * Label to apply to the workflow run.
2787
+ * Labels attached to the workflow run.
2737
2788
  *
2738
- * Can be used to filter the workflow run logs.
2789
+ * Can be used to filter the workflow run logs. A run can have multiple
2790
+ * labels when triggered with `label: string[]`.
2739
2791
  *
2740
2792
  * Can be set by passing a `label` parameter when triggering the workflow
2741
2793
  * with `client.trigger`:
@@ -2743,11 +2795,20 @@ var WorkflowContext = class {
2743
2795
  * ```ts
2744
2796
  * await client.trigger({
2745
2797
  * url: "https://workflow-endpoint.com",
2746
- * label: "my-label"
2798
+ * label: ["label-1", "label-2"]
2747
2799
  * });
2748
2800
  * ```
2749
2801
  */
2750
- label;
2802
+ labels;
2803
+ /**
2804
+ * Label of the workflow run.
2805
+ *
2806
+ * @deprecated Use `labels` instead. When a run has multiple labels, this
2807
+ * only returns the first one.
2808
+ */
2809
+ get label() {
2810
+ return this.labels[0];
2811
+ }
2751
2812
  /**
2752
2813
  * Number of times QStash has retried delivering the current request.
2753
2814
  *
@@ -2778,7 +2839,7 @@ var WorkflowContext = class {
2778
2839
  this.headers = headers;
2779
2840
  this.requestPayload = initialPayload;
2780
2841
  this.env = env ?? {};
2781
- this.label = label;
2842
+ this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
2782
2843
  this.retried = retried ?? 0;
2783
2844
  const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
2784
2845
  middlewareManagerInstance.assignContext(this);
@@ -2859,8 +2920,10 @@ var WorkflowContext = class {
2859
2920
  await this.addStep(new LazySleepUntilStep(this, stepName, time));
2860
2921
  }
2861
2922
  async call(stepName, settings) {
2923
+ validateFlowControl(settings.flowControl);
2862
2924
  let callStep;
2863
2925
  if ("workflow" in settings) {
2926
+ validateLabel(settings.label);
2864
2927
  const url = getNewUrlFromWorkflowId(this.url, settings.workflow.workflowId);
2865
2928
  const stringBody = typeof settings.body === "string" ? settings.body : settings.body === void 0 ? void 0 : JSON.stringify(settings.body);
2866
2929
  callStep = new LazyCallStep({
@@ -2968,6 +3031,8 @@ var WorkflowContext = class {
2968
3031
  );
2969
3032
  }
2970
3033
  async invoke(stepName, settings) {
3034
+ validateLabel(settings.label);
3035
+ validateFlowControl(settings.flowControl);
2971
3036
  return await this.addStep(
2972
3037
  new LazyInvokeStep(this, stepName, settings)
2973
3038
  );
@@ -3127,7 +3192,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
3127
3192
  url: context.url,
3128
3193
  initialPayload: context.requestPayload,
3129
3194
  env: context.env,
3130
- label: context.label,
3195
+ label: context.labels,
3131
3196
  retried: context.retried
3132
3197
  });
3133
3198
  try {
@@ -3891,10 +3956,12 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3891
3956
  await middlewareManager.dispatchDebug("onError", {
3892
3957
  error: isInstanceOf(error, Error) ? error : new Error(formattedError.message)
3893
3958
  });
3959
+ const stepName = getStepNameFromError(error)?.replace(/[\u0000-\u001F\u007F]+/g, " ").trim();
3894
3960
  return new Response(JSON.stringify(formattedError), {
3895
3961
  status: 500,
3896
3962
  headers: {
3897
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3963
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
3964
+ ...stepName ? { [WORKFLOW_ERROR_STEP_NAME_HEADER]: stepName } : {}
3898
3965
  }
3899
3966
  });
3900
3967
  }
@@ -3923,6 +3990,9 @@ export {
3923
3990
  buildBulkActionQueryParameters,
3924
3991
  WORKFLOW_LABEL_HEADER,
3925
3992
  SDK_TELEMETRY,
3993
+ validateLabel,
3994
+ serializeLabel,
3995
+ validateFlowControl,
3926
3996
  getWorkflowRunId,
3927
3997
  StepTypes,
3928
3998
  triggerFirstInvocation,
package/cloudflare.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { c as RouteFunction, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-B_E1VAK6.mjs';
2
- import { s as serveManyBase } from './serve-many-D3D9uE4u.mjs';
1
+ import { c as RouteFunction, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-CekOpKvz.mjs';
2
+ import { s as serveManyBase } from './serve-many-CG3BFvO3.mjs';
3
3
  import '@upstash/qstash';
4
4
  import 'zod';
5
5
 
package/cloudflare.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { c as RouteFunction, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-B_E1VAK6.js';
2
- import { s as serveManyBase } from './serve-many-kPOasiyb.js';
1
+ import { c as RouteFunction, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-CekOpKvz.js';
2
+ import { s as serveManyBase } from './serve-many-iJF1IUXk.js';
3
3
  import '@upstash/qstash';
4
4
  import 'zod';
5
5
 
package/cloudflare.js CHANGED
@@ -39,12 +39,13 @@ var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
39
39
  var WORKFLOW_LABEL_HEADER = "Upstash-Label";
40
40
  var WORKFLOW_UNKOWN_SDK_VERSION_HEADER = "Upstash-Workflow-Unknown-Sdk";
41
41
  var WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER = "upstash-workflow-trigger-by-sdk";
42
+ var WORKFLOW_ERROR_STEP_NAME_HEADER = "Upstash-Error-Step-Name";
42
43
  var WORKFLOW_PROTOCOL_VERSION = "1";
43
44
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
44
45
  var DEFAULT_CONTENT_TYPE = "application/json";
45
46
  var NO_CONCURRENCY = 1;
46
47
  var DEFAULT_RETRIES = 3;
47
- var VERSION = "v1.2.1";
48
+ var VERSION = "v1.3.2";
48
49
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
49
50
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
50
51
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -127,6 +128,22 @@ var formatWorkflowError = (error) => {
127
128
  message: `An error occured while executing workflow: '${typeof error === "string" ? error : JSON.stringify(error)}'`
128
129
  };
129
130
  };
131
+ var attachStepNameToError = (error, stepName) => {
132
+ if (typeof error !== "object" || error === null) return;
133
+ if ("errorStepName" in error) return;
134
+ if (!Object.isExtensible(error)) return;
135
+ try {
136
+ error.errorStepName = stepName;
137
+ } catch {
138
+ }
139
+ };
140
+ var getStepNameFromError = (error) => {
141
+ if (typeof error === "object" && error !== null) {
142
+ const stepName = error.errorStepName;
143
+ return typeof stepName === "string" ? stepName : void 0;
144
+ }
145
+ return void 0;
146
+ };
130
147
  function getConstructorName(obj) {
131
148
  if (obj === null || obj === void 0) {
132
149
  return null;
@@ -216,6 +233,31 @@ var import_qstash4 = require("@upstash/qstash");
216
233
  // src/utils.ts
217
234
  var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
218
235
  var NANOID_LENGTH = 21;
236
+ var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
237
+ function validateLabel(label) {
238
+ if (label === void 0) return;
239
+ const labels = Array.isArray(label) ? label : [label];
240
+ if (labels.length === 0) {
241
+ throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
242
+ }
243
+ for (const value of labels) {
244
+ if (!RESOURCE_NAME_PATTERN.test(value)) {
245
+ throw new WorkflowNonRetryableError(
246
+ `Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
247
+ );
248
+ }
249
+ }
250
+ }
251
+ function serializeLabel(label) {
252
+ return Array.isArray(label) ? label.join(",") : label;
253
+ }
254
+ function validateFlowControl(flowControl) {
255
+ if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
256
+ throw new WorkflowNonRetryableError(
257
+ `Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
258
+ );
259
+ }
260
+ }
219
261
  function getRandomInt() {
220
262
  return Math.floor(Math.random() * NANOID_CHARS.length);
221
263
  }
@@ -751,8 +793,8 @@ var triggerFirstInvocation = async (params) => {
751
793
  if (unknownSdk) {
752
794
  headers[WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER] = "true";
753
795
  }
754
- if (workflowContext.label) {
755
- headers[WORKFLOW_LABEL_HEADER] = workflowContext.label;
796
+ if (workflowContext.labels.length > 0) {
797
+ headers[WORKFLOW_LABEL_HEADER] = serializeLabel(workflowContext.labels);
756
798
  }
757
799
  const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
758
800
  return {
@@ -1554,8 +1596,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1554
1596
  });
1555
1597
  triggerHeaders["Upstash-Workflow-Invoke"] = "true";
1556
1598
  if (label) {
1557
- triggerHeaders[WORKFLOW_LABEL_HEADER] = label;
1558
- triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = label;
1599
+ const labelHeader = serializeLabel(label);
1600
+ triggerHeaders[WORKFLOW_LABEL_HEADER] = labelHeader;
1601
+ triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = labelHeader;
1559
1602
  }
1560
1603
  return { headers: triggerHeaders, contentType };
1561
1604
  }
@@ -1898,7 +1941,13 @@ var submitSingleStep = async ({
1898
1941
  await dispatchLifecycle("beforeExecution", {
1899
1942
  stepName: lazyStep.stepName
1900
1943
  });
1901
- const resultStep = await lazyStep.getResultStep(concurrency, stepId);
1944
+ let resultStep;
1945
+ try {
1946
+ resultStep = await lazyStep.getResultStep(concurrency, stepId);
1947
+ } catch (error) {
1948
+ attachStepNameToError(error, lazyStep.stepName);
1949
+ throw error;
1950
+ }
1902
1951
  const { headers } = lazyStep.getHeaders({
1903
1952
  context,
1904
1953
  step: resultStep,
@@ -2121,9 +2170,11 @@ var AutoExecutor = class _AutoExecutor {
2121
2170
  if (isInstanceOf(error, WorkflowAbort) || isInstanceOf(error, import_qstash5.QstashError) && error.status === 400 || isInstanceOf(error, import_qstash5.QstashError) && error.status === 412) {
2122
2171
  throw error;
2123
2172
  }
2124
- throw new WorkflowError(
2173
+ const wrappedError = new WorkflowError(
2125
2174
  `Error submitting steps to QStash in partial parallel step execution: ${error}`
2126
2175
  );
2176
+ attachStepNameToError(wrappedError, parallelSteps[stepIndex].stepName);
2177
+ throw wrappedError;
2127
2178
  }
2128
2179
  break;
2129
2180
  }
@@ -2696,9 +2747,10 @@ var WorkflowContext = class {
2696
2747
  */
2697
2748
  env;
2698
2749
  /**
2699
- * Label to apply to the workflow run.
2750
+ * Labels attached to the workflow run.
2700
2751
  *
2701
- * Can be used to filter the workflow run logs.
2752
+ * Can be used to filter the workflow run logs. A run can have multiple
2753
+ * labels when triggered with `label: string[]`.
2702
2754
  *
2703
2755
  * Can be set by passing a `label` parameter when triggering the workflow
2704
2756
  * with `client.trigger`:
@@ -2706,11 +2758,20 @@ var WorkflowContext = class {
2706
2758
  * ```ts
2707
2759
  * await client.trigger({
2708
2760
  * url: "https://workflow-endpoint.com",
2709
- * label: "my-label"
2761
+ * label: ["label-1", "label-2"]
2710
2762
  * });
2711
2763
  * ```
2712
2764
  */
2713
- label;
2765
+ labels;
2766
+ /**
2767
+ * Label of the workflow run.
2768
+ *
2769
+ * @deprecated Use `labels` instead. When a run has multiple labels, this
2770
+ * only returns the first one.
2771
+ */
2772
+ get label() {
2773
+ return this.labels[0];
2774
+ }
2714
2775
  /**
2715
2776
  * Number of times QStash has retried delivering the current request.
2716
2777
  *
@@ -2741,7 +2802,7 @@ var WorkflowContext = class {
2741
2802
  this.headers = headers;
2742
2803
  this.requestPayload = initialPayload;
2743
2804
  this.env = env ?? {};
2744
- this.label = label;
2805
+ this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
2745
2806
  this.retried = retried ?? 0;
2746
2807
  const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
2747
2808
  middlewareManagerInstance.assignContext(this);
@@ -2822,8 +2883,10 @@ var WorkflowContext = class {
2822
2883
  await this.addStep(new LazySleepUntilStep(this, stepName, time));
2823
2884
  }
2824
2885
  async call(stepName, settings) {
2886
+ validateFlowControl(settings.flowControl);
2825
2887
  let callStep;
2826
2888
  if ("workflow" in settings) {
2889
+ validateLabel(settings.label);
2827
2890
  const url = getNewUrlFromWorkflowId(this.url, settings.workflow.workflowId);
2828
2891
  const stringBody = typeof settings.body === "string" ? settings.body : settings.body === void 0 ? void 0 : JSON.stringify(settings.body);
2829
2892
  callStep = new LazyCallStep({
@@ -2931,6 +2994,8 @@ var WorkflowContext = class {
2931
2994
  );
2932
2995
  }
2933
2996
  async invoke(stepName, settings) {
2997
+ validateLabel(settings.label);
2998
+ validateFlowControl(settings.flowControl);
2934
2999
  return await this.addStep(
2935
3000
  new LazyInvokeStep(this, stepName, settings)
2936
3001
  );
@@ -3016,7 +3081,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
3016
3081
  url: context.url,
3017
3082
  initialPayload: context.requestPayload,
3018
3083
  env: context.env,
3019
- label: context.label,
3084
+ label: context.labels,
3020
3085
  retried: context.retried
3021
3086
  });
3022
3087
  try {
@@ -3854,10 +3919,12 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
3854
3919
  await middlewareManager.dispatchDebug("onError", {
3855
3920
  error: isInstanceOf(error, Error) ? error : new Error(formattedError.message)
3856
3921
  });
3922
+ const stepName = getStepNameFromError(error)?.replace(/[\u0000-\u001F\u007F]+/g, " ").trim();
3857
3923
  return new Response(JSON.stringify(formattedError), {
3858
3924
  status: 500,
3859
3925
  headers: {
3860
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3926
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
3927
+ ...stepName ? { [WORKFLOW_ERROR_STEP_NAME_HEADER]: stepName } : {}
3861
3928
  }
3862
3929
  });
3863
3930
  }
package/cloudflare.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  SDK_TELEMETRY,
3
3
  serveBase,
4
4
  serveManyBase
5
- } from "./chunk-G24J5PCC.mjs";
5
+ } from "./chunk-ICDNN4UW.mjs";
6
6
 
7
7
  // platforms/cloudflare.ts
8
8
  var getArgs = (args) => {