@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/solidjs.js CHANGED
@@ -101,6 +101,22 @@ var formatWorkflowError = (error) => {
101
101
  message: `An error occured while executing workflow: '${typeof error === "string" ? error : JSON.stringify(error)}'`
102
102
  };
103
103
  };
104
+ var attachStepNameToError = (error, stepName) => {
105
+ if (typeof error !== "object" || error === null) return;
106
+ if ("errorStepName" in error) return;
107
+ if (!Object.isExtensible(error)) return;
108
+ try {
109
+ error.errorStepName = stepName;
110
+ } catch {
111
+ }
112
+ };
113
+ var getStepNameFromError = (error) => {
114
+ if (typeof error === "object" && error !== null) {
115
+ const stepName = error.errorStepName;
116
+ return typeof stepName === "string" ? stepName : void 0;
117
+ }
118
+ return void 0;
119
+ };
104
120
  function getConstructorName(obj) {
105
121
  if (obj === null || obj === void 0) {
106
122
  return null;
@@ -194,12 +210,13 @@ var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
194
210
  var WORKFLOW_LABEL_HEADER = "Upstash-Label";
195
211
  var WORKFLOW_UNKOWN_SDK_VERSION_HEADER = "Upstash-Workflow-Unknown-Sdk";
196
212
  var WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER = "upstash-workflow-trigger-by-sdk";
213
+ var WORKFLOW_ERROR_STEP_NAME_HEADER = "Upstash-Error-Step-Name";
197
214
  var WORKFLOW_PROTOCOL_VERSION = "1";
198
215
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
199
216
  var DEFAULT_CONTENT_TYPE = "application/json";
200
217
  var NO_CONCURRENCY = 1;
201
218
  var DEFAULT_RETRIES = 3;
202
- var VERSION = "v1.2.1";
219
+ var VERSION = "v1.3.2";
203
220
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
204
221
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
205
222
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -214,6 +231,31 @@ var import_qstash4 = require("@upstash/qstash");
214
231
  // src/utils.ts
215
232
  var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
216
233
  var NANOID_LENGTH = 21;
234
+ var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
235
+ function validateLabel(label) {
236
+ if (label === void 0) return;
237
+ const labels = Array.isArray(label) ? label : [label];
238
+ if (labels.length === 0) {
239
+ throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
240
+ }
241
+ for (const value of labels) {
242
+ if (!RESOURCE_NAME_PATTERN.test(value)) {
243
+ throw new WorkflowNonRetryableError(
244
+ `Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
245
+ );
246
+ }
247
+ }
248
+ }
249
+ function serializeLabel(label) {
250
+ return Array.isArray(label) ? label.join(",") : label;
251
+ }
252
+ function validateFlowControl(flowControl) {
253
+ if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
254
+ throw new WorkflowNonRetryableError(
255
+ `Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
256
+ );
257
+ }
258
+ }
217
259
  function getRandomInt() {
218
260
  return Math.floor(Math.random() * NANOID_CHARS.length);
219
261
  }
@@ -749,8 +791,8 @@ var triggerFirstInvocation = async (params) => {
749
791
  if (unknownSdk) {
750
792
  headers[WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER] = "true";
751
793
  }
752
- if (workflowContext.label) {
753
- headers[WORKFLOW_LABEL_HEADER] = workflowContext.label;
794
+ if (workflowContext.labels.length > 0) {
795
+ headers[WORKFLOW_LABEL_HEADER] = serializeLabel(workflowContext.labels);
754
796
  }
755
797
  const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
756
798
  return {
@@ -1552,8 +1594,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1552
1594
  });
1553
1595
  triggerHeaders["Upstash-Workflow-Invoke"] = "true";
1554
1596
  if (label) {
1555
- triggerHeaders[WORKFLOW_LABEL_HEADER] = label;
1556
- triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = label;
1597
+ const labelHeader = serializeLabel(label);
1598
+ triggerHeaders[WORKFLOW_LABEL_HEADER] = labelHeader;
1599
+ triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = labelHeader;
1557
1600
  }
1558
1601
  return { headers: triggerHeaders, contentType };
1559
1602
  }
@@ -1896,7 +1939,13 @@ var submitSingleStep = async ({
1896
1939
  await dispatchLifecycle("beforeExecution", {
1897
1940
  stepName: lazyStep.stepName
1898
1941
  });
1899
- const resultStep = await lazyStep.getResultStep(concurrency, stepId);
1942
+ let resultStep;
1943
+ try {
1944
+ resultStep = await lazyStep.getResultStep(concurrency, stepId);
1945
+ } catch (error) {
1946
+ attachStepNameToError(error, lazyStep.stepName);
1947
+ throw error;
1948
+ }
1900
1949
  const { headers } = lazyStep.getHeaders({
1901
1950
  context,
1902
1951
  step: resultStep,
@@ -2119,9 +2168,11 @@ var AutoExecutor = class _AutoExecutor {
2119
2168
  if (isInstanceOf(error, WorkflowAbort) || isInstanceOf(error, import_qstash5.QstashError) && error.status === 400 || isInstanceOf(error, import_qstash5.QstashError) && error.status === 412) {
2120
2169
  throw error;
2121
2170
  }
2122
- throw new WorkflowError(
2171
+ const wrappedError = new WorkflowError(
2123
2172
  `Error submitting steps to QStash in partial parallel step execution: ${error}`
2124
2173
  );
2174
+ attachStepNameToError(wrappedError, parallelSteps[stepIndex].stepName);
2175
+ throw wrappedError;
2125
2176
  }
2126
2177
  break;
2127
2178
  }
@@ -2627,9 +2678,10 @@ var WorkflowContext = class {
2627
2678
  */
2628
2679
  env;
2629
2680
  /**
2630
- * Label to apply to the workflow run.
2681
+ * Labels attached to the workflow run.
2631
2682
  *
2632
- * Can be used to filter the workflow run logs.
2683
+ * Can be used to filter the workflow run logs. A run can have multiple
2684
+ * labels when triggered with `label: string[]`.
2633
2685
  *
2634
2686
  * Can be set by passing a `label` parameter when triggering the workflow
2635
2687
  * with `client.trigger`:
@@ -2637,11 +2689,20 @@ var WorkflowContext = class {
2637
2689
  * ```ts
2638
2690
  * await client.trigger({
2639
2691
  * url: "https://workflow-endpoint.com",
2640
- * label: "my-label"
2692
+ * label: ["label-1", "label-2"]
2641
2693
  * });
2642
2694
  * ```
2643
2695
  */
2644
- label;
2696
+ labels;
2697
+ /**
2698
+ * Label of the workflow run.
2699
+ *
2700
+ * @deprecated Use `labels` instead. When a run has multiple labels, this
2701
+ * only returns the first one.
2702
+ */
2703
+ get label() {
2704
+ return this.labels[0];
2705
+ }
2645
2706
  /**
2646
2707
  * Number of times QStash has retried delivering the current request.
2647
2708
  *
@@ -2672,7 +2733,7 @@ var WorkflowContext = class {
2672
2733
  this.headers = headers;
2673
2734
  this.requestPayload = initialPayload;
2674
2735
  this.env = env ?? {};
2675
- this.label = label;
2736
+ this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
2676
2737
  this.retried = retried ?? 0;
2677
2738
  const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
2678
2739
  middlewareManagerInstance.assignContext(this);
@@ -2753,8 +2814,10 @@ var WorkflowContext = class {
2753
2814
  await this.addStep(new LazySleepUntilStep(this, stepName, time));
2754
2815
  }
2755
2816
  async call(stepName, settings) {
2817
+ validateFlowControl(settings.flowControl);
2756
2818
  let callStep;
2757
2819
  if ("workflow" in settings) {
2820
+ validateLabel(settings.label);
2758
2821
  const url = getNewUrlFromWorkflowId(this.url, settings.workflow.workflowId);
2759
2822
  const stringBody = typeof settings.body === "string" ? settings.body : settings.body === void 0 ? void 0 : JSON.stringify(settings.body);
2760
2823
  callStep = new LazyCallStep({
@@ -2862,6 +2925,8 @@ var WorkflowContext = class {
2862
2925
  );
2863
2926
  }
2864
2927
  async invoke(stepName, settings) {
2928
+ validateLabel(settings.label);
2929
+ validateFlowControl(settings.flowControl);
2865
2930
  return await this.addStep(
2866
2931
  new LazyInvokeStep(this, stepName, settings)
2867
2932
  );
@@ -2947,7 +3012,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
2947
3012
  url: context.url,
2948
3013
  initialPayload: context.requestPayload,
2949
3014
  env: context.env,
2950
- label: context.label,
3015
+ label: context.labels,
2951
3016
  retried: context.retried
2952
3017
  });
2953
3018
  try {
@@ -3785,10 +3850,12 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3785
3850
  await middlewareManager.dispatchDebug("onError", {
3786
3851
  error: isInstanceOf(error, Error) ? error : new Error(formattedError.message)
3787
3852
  });
3853
+ const stepName = getStepNameFromError(error)?.replace(/[\u0000-\u001F\u007F]+/g, " ").trim();
3788
3854
  return new Response(JSON.stringify(formattedError), {
3789
3855
  status: 500,
3790
3856
  headers: {
3791
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3857
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
3858
+ ...stepName ? { [WORKFLOW_ERROR_STEP_NAME_HEADER]: stepName } : {}
3792
3859
  }
3793
3860
  });
3794
3861
  }
package/solidjs.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SDK_TELEMETRY,
3
3
  serveBase
4
- } from "./chunk-G24J5PCC.mjs";
4
+ } from "./chunk-ICDNN4UW.mjs";
5
5
 
6
6
  // platforms/solidjs.ts
7
7
  var serve = (routeFunction, options) => {
package/svelte.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _sveltejs_kit from '@sveltejs/kit';
2
2
  import { RequestHandler } from '@sveltejs/kit';
3
- import { c as RouteFunction, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-B_E1VAK6.mjs';
4
- import { s as serveManyBase } from './serve-many-D3D9uE4u.mjs';
3
+ import { c as RouteFunction, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-CekOpKvz.mjs';
4
+ import { s as serveManyBase } from './serve-many-CG3BFvO3.mjs';
5
5
  import '@upstash/qstash';
6
6
  import 'zod';
7
7
 
package/svelte.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _sveltejs_kit from '@sveltejs/kit';
2
2
  import { RequestHandler } from '@sveltejs/kit';
3
- import { c as RouteFunction, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-B_E1VAK6.js';
4
- import { s as serveManyBase } from './serve-many-kPOasiyb.js';
3
+ import { c as RouteFunction, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-CekOpKvz.js';
4
+ import { s as serveManyBase } from './serve-many-iJF1IUXk.js';
5
5
  import '@upstash/qstash';
6
6
  import 'zod';
7
7
 
package/svelte.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/svelte.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/svelte.ts
8
8
  var telemetry = {
package/tanstack.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { c as RouteFunction, d as WorkflowServeOptions, E as ExclusiveValidationOptions, 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, E as ExclusiveValidationOptions, 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/tanstack.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { c as RouteFunction, d as WorkflowServeOptions, E as ExclusiveValidationOptions, 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, E as ExclusiveValidationOptions, 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/tanstack.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
  }