@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/nextjs.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';
2
- import { c as RouteFunction, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-B_E1VAK6.mjs';
3
- import { s as serveManyBase } from './serve-many-D3D9uE4u.mjs';
2
+ import { c as RouteFunction, 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/nextjs.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';
2
- import { c as RouteFunction, d as WorkflowServeOptions, I as InvokableWorkflow } from './types-B_E1VAK6.js';
3
- import { s as serveManyBase } from './serve-many-kPOasiyb.js';
2
+ import { c as RouteFunction, 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/nextjs.js CHANGED
@@ -106,6 +106,22 @@ var formatWorkflowError = (error) => {
106
106
  message: `An error occured while executing workflow: '${typeof error === "string" ? error : JSON.stringify(error)}'`
107
107
  };
108
108
  };
109
+ var attachStepNameToError = (error, stepName) => {
110
+ if (typeof error !== "object" || error === null) return;
111
+ if ("errorStepName" in error) return;
112
+ if (!Object.isExtensible(error)) return;
113
+ try {
114
+ error.errorStepName = stepName;
115
+ } catch {
116
+ }
117
+ };
118
+ var getStepNameFromError = (error) => {
119
+ if (typeof error === "object" && error !== null) {
120
+ const stepName = error.errorStepName;
121
+ return typeof stepName === "string" ? stepName : void 0;
122
+ }
123
+ return void 0;
124
+ };
109
125
  function getConstructorName(obj) {
110
126
  if (obj === null || obj === void 0) {
111
127
  return null;
@@ -199,12 +215,13 @@ var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
199
215
  var WORKFLOW_LABEL_HEADER = "Upstash-Label";
200
216
  var WORKFLOW_UNKOWN_SDK_VERSION_HEADER = "Upstash-Workflow-Unknown-Sdk";
201
217
  var WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER = "upstash-workflow-trigger-by-sdk";
218
+ var WORKFLOW_ERROR_STEP_NAME_HEADER = "Upstash-Error-Step-Name";
202
219
  var WORKFLOW_PROTOCOL_VERSION = "1";
203
220
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
204
221
  var DEFAULT_CONTENT_TYPE = "application/json";
205
222
  var NO_CONCURRENCY = 1;
206
223
  var DEFAULT_RETRIES = 3;
207
- var VERSION = "v1.2.1";
224
+ var VERSION = "v1.3.2";
208
225
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
209
226
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
210
227
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -219,6 +236,31 @@ var import_qstash4 = require("@upstash/qstash");
219
236
  // src/utils.ts
220
237
  var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
221
238
  var NANOID_LENGTH = 21;
239
+ var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
240
+ function validateLabel(label) {
241
+ if (label === void 0) return;
242
+ const labels = Array.isArray(label) ? label : [label];
243
+ if (labels.length === 0) {
244
+ throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
245
+ }
246
+ for (const value of labels) {
247
+ if (!RESOURCE_NAME_PATTERN.test(value)) {
248
+ throw new WorkflowNonRetryableError(
249
+ `Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
250
+ );
251
+ }
252
+ }
253
+ }
254
+ function serializeLabel(label) {
255
+ return Array.isArray(label) ? label.join(",") : label;
256
+ }
257
+ function validateFlowControl(flowControl) {
258
+ if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
259
+ throw new WorkflowNonRetryableError(
260
+ `Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
261
+ );
262
+ }
263
+ }
222
264
  function getRandomInt() {
223
265
  return Math.floor(Math.random() * NANOID_CHARS.length);
224
266
  }
@@ -754,8 +796,8 @@ var triggerFirstInvocation = async (params) => {
754
796
  if (unknownSdk) {
755
797
  headers[WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER] = "true";
756
798
  }
757
- if (workflowContext.label) {
758
- headers[WORKFLOW_LABEL_HEADER] = workflowContext.label;
799
+ if (workflowContext.labels.length > 0) {
800
+ headers[WORKFLOW_LABEL_HEADER] = serializeLabel(workflowContext.labels);
759
801
  }
760
802
  const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
761
803
  return {
@@ -1557,8 +1599,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1557
1599
  });
1558
1600
  triggerHeaders["Upstash-Workflow-Invoke"] = "true";
1559
1601
  if (label) {
1560
- triggerHeaders[WORKFLOW_LABEL_HEADER] = label;
1561
- triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = label;
1602
+ const labelHeader = serializeLabel(label);
1603
+ triggerHeaders[WORKFLOW_LABEL_HEADER] = labelHeader;
1604
+ triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = labelHeader;
1562
1605
  }
1563
1606
  return { headers: triggerHeaders, contentType };
1564
1607
  }
@@ -1901,7 +1944,13 @@ var submitSingleStep = async ({
1901
1944
  await dispatchLifecycle("beforeExecution", {
1902
1945
  stepName: lazyStep.stepName
1903
1946
  });
1904
- const resultStep = await lazyStep.getResultStep(concurrency, stepId);
1947
+ let resultStep;
1948
+ try {
1949
+ resultStep = await lazyStep.getResultStep(concurrency, stepId);
1950
+ } catch (error) {
1951
+ attachStepNameToError(error, lazyStep.stepName);
1952
+ throw error;
1953
+ }
1905
1954
  const { headers } = lazyStep.getHeaders({
1906
1955
  context,
1907
1956
  step: resultStep,
@@ -2124,9 +2173,11 @@ var AutoExecutor = class _AutoExecutor {
2124
2173
  if (isInstanceOf(error, WorkflowAbort) || isInstanceOf(error, import_qstash5.QstashError) && error.status === 400 || isInstanceOf(error, import_qstash5.QstashError) && error.status === 412) {
2125
2174
  throw error;
2126
2175
  }
2127
- throw new WorkflowError(
2176
+ const wrappedError = new WorkflowError(
2128
2177
  `Error submitting steps to QStash in partial parallel step execution: ${error}`
2129
2178
  );
2179
+ attachStepNameToError(wrappedError, parallelSteps[stepIndex].stepName);
2180
+ throw wrappedError;
2130
2181
  }
2131
2182
  break;
2132
2183
  }
@@ -2699,9 +2750,10 @@ var WorkflowContext = class {
2699
2750
  */
2700
2751
  env;
2701
2752
  /**
2702
- * Label to apply to the workflow run.
2753
+ * Labels attached to the workflow run.
2703
2754
  *
2704
- * Can be used to filter the workflow run logs.
2755
+ * Can be used to filter the workflow run logs. A run can have multiple
2756
+ * labels when triggered with `label: string[]`.
2705
2757
  *
2706
2758
  * Can be set by passing a `label` parameter when triggering the workflow
2707
2759
  * with `client.trigger`:
@@ -2709,11 +2761,20 @@ var WorkflowContext = class {
2709
2761
  * ```ts
2710
2762
  * await client.trigger({
2711
2763
  * url: "https://workflow-endpoint.com",
2712
- * label: "my-label"
2764
+ * label: ["label-1", "label-2"]
2713
2765
  * });
2714
2766
  * ```
2715
2767
  */
2716
- label;
2768
+ labels;
2769
+ /**
2770
+ * Label of the workflow run.
2771
+ *
2772
+ * @deprecated Use `labels` instead. When a run has multiple labels, this
2773
+ * only returns the first one.
2774
+ */
2775
+ get label() {
2776
+ return this.labels[0];
2777
+ }
2717
2778
  /**
2718
2779
  * Number of times QStash has retried delivering the current request.
2719
2780
  *
@@ -2744,7 +2805,7 @@ var WorkflowContext = class {
2744
2805
  this.headers = headers;
2745
2806
  this.requestPayload = initialPayload;
2746
2807
  this.env = env ?? {};
2747
- this.label = label;
2808
+ this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
2748
2809
  this.retried = retried ?? 0;
2749
2810
  const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
2750
2811
  middlewareManagerInstance.assignContext(this);
@@ -2825,8 +2886,10 @@ var WorkflowContext = class {
2825
2886
  await this.addStep(new LazySleepUntilStep(this, stepName, time));
2826
2887
  }
2827
2888
  async call(stepName, settings) {
2889
+ validateFlowControl(settings.flowControl);
2828
2890
  let callStep;
2829
2891
  if ("workflow" in settings) {
2892
+ validateLabel(settings.label);
2830
2893
  const url = getNewUrlFromWorkflowId(this.url, settings.workflow.workflowId);
2831
2894
  const stringBody = typeof settings.body === "string" ? settings.body : settings.body === void 0 ? void 0 : JSON.stringify(settings.body);
2832
2895
  callStep = new LazyCallStep({
@@ -2934,6 +2997,8 @@ var WorkflowContext = class {
2934
2997
  );
2935
2998
  }
2936
2999
  async invoke(stepName, settings) {
3000
+ validateLabel(settings.label);
3001
+ validateFlowControl(settings.flowControl);
2937
3002
  return await this.addStep(
2938
3003
  new LazyInvokeStep(this, stepName, settings)
2939
3004
  );
@@ -3019,7 +3084,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
3019
3084
  url: context.url,
3020
3085
  initialPayload: context.requestPayload,
3021
3086
  env: context.env,
3022
- label: context.label,
3087
+ label: context.labels,
3023
3088
  retried: context.retried
3024
3089
  });
3025
3090
  try {
@@ -3857,10 +3922,12 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3857
3922
  await middlewareManager.dispatchDebug("onError", {
3858
3923
  error: isInstanceOf(error, Error) ? error : new Error(formattedError.message)
3859
3924
  });
3925
+ const stepName = getStepNameFromError(error)?.replace(/[\u0000-\u001F\u007F]+/g, " ").trim();
3860
3926
  return new Response(JSON.stringify(formattedError), {
3861
3927
  status: 500,
3862
3928
  headers: {
3863
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3929
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
3930
+ ...stepName ? { [WORKFLOW_ERROR_STEP_NAME_HEADER]: stepName } : {}
3864
3931
  }
3865
3932
  });
3866
3933
  }
package/nextjs.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/nextjs.ts
8
8
  var appTelemetry = {
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@upstash/workflow","version":"1.3.0","description":"Durable, Reliable and Performant Serverless Functions","main":"./index.js","module":"./index.mjs","types":"./index.d.ts","files":["./*"],"exports":{".":{"import":"./index.mjs","require":"./index.js"},"./dist/nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./h3":{"import":"./h3.mjs","require":"./h3.js"},"./svelte":{"import":"./svelte.mjs","require":"./svelte.js"},"./solidjs":{"import":"./solidjs.mjs","require":"./solidjs.js"},"./workflow":{"import":"./workflow.mjs","require":"./workflow.js"},"./hono":{"import":"./hono.mjs","require":"./hono.js"},"./cloudflare":{"import":"./cloudflare.mjs","require":"./cloudflare.js"},"./astro":{"import":"./astro.mjs","require":"./astro.js"},"./express":{"import":"./express.mjs","require":"./express.js"},"./tanstack":{"import":"./tanstack.mjs","require":"./tanstack.js"},"./react-router":{"import":"./react-router.mjs","require":"./react-router.js"}},"scripts":{"build":"tsup && cp README.md ./dist/ && cp package.json ./dist/ && cp LICENSE ./dist/","test":"bun test src","fmt":"prettier --write .","lint":"tsc && eslint \"{src,platforms}/**/*.{js,ts,tsx}\" --quiet --fix","check-exports":"bun run build && cd dist && attw -P"},"repository":{"type":"git","url":"git@github.com:upstash/workflow-js.git"},"keywords":["upstash","qstash","workflow","serverless"],"author":"Cahid Arda Oz","license":"MIT","bugs":{"url":"https://github.com/upstash/workflow-ts/issues"},"homepage":"https://github.com/upstash/workflow-ts#readme","devDependencies":{"@commitlint/cli":"^19.5.0","@commitlint/config-conventional":"^19.5.0","@eslint/js":"^9.11.1","@solidjs/start":"^1.0.8","@sveltejs/kit":"^2.6.1","@types/bun":"^1.1.10","@types/express":"^5.0.6","astro":"^4.16.7","eslint":"^9.11.1","eslint-plugin-unicorn":"^55.0.0","express":"^5.1.0","globals":"^15.10.0","h3":"^1.12.0","hono":"^4.6.20","husky":"^9.1.6","next":"^14.2.14","prettier":"3.3.3","tsup":"^8.3.0","typescript":"^5.7.2","typescript-eslint":"^8.18.0"},"dependencies":{"@upstash/qstash":"^2.11.0"},"directories":{"example":"examples"},"peerDependencies":{"zod":"^3.25.0 || ^4.0.0"}}
1
+ {"name":"@upstash/workflow","version":"1.3.2-rc","description":"Durable, Reliable and Performant Serverless Functions","main":"./index.js","module":"./index.mjs","types":"./index.d.ts","files":["./*"],"exports":{".":{"import":"./index.mjs","require":"./index.js"},"./dist/nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./h3":{"import":"./h3.mjs","require":"./h3.js"},"./svelte":{"import":"./svelte.mjs","require":"./svelte.js"},"./solidjs":{"import":"./solidjs.mjs","require":"./solidjs.js"},"./workflow":{"import":"./workflow.mjs","require":"./workflow.js"},"./hono":{"import":"./hono.mjs","require":"./hono.js"},"./cloudflare":{"import":"./cloudflare.mjs","require":"./cloudflare.js"},"./astro":{"import":"./astro.mjs","require":"./astro.js"},"./express":{"import":"./express.mjs","require":"./express.js"},"./tanstack":{"import":"./tanstack.mjs","require":"./tanstack.js"},"./react-router":{"import":"./react-router.mjs","require":"./react-router.js"}},"scripts":{"build":"tsup && cp README.md ./dist/ && cp package.json ./dist/ && cp LICENSE ./dist/","test":"bun test src","fmt":"prettier --write .","lint":"tsc && eslint \"{src,platforms}/**/*.{js,ts,tsx}\" --quiet --fix","check-exports":"bun run build && cd dist && attw -P"},"repository":{"type":"git","url":"git@github.com:upstash/workflow-js.git"},"keywords":["upstash","qstash","workflow","serverless"],"author":"Cahid Arda Oz","license":"MIT","bugs":{"url":"https://github.com/upstash/workflow-ts/issues"},"homepage":"https://github.com/upstash/workflow-ts#readme","devDependencies":{"@commitlint/cli":"^19.5.0","@commitlint/config-conventional":"^19.5.0","@eslint/js":"^9.11.1","@solidjs/start":"^1.0.8","@sveltejs/kit":"^2.6.1","@types/bun":"^1.1.10","@types/express":"^5.0.6","astro":"^4.16.7","eslint":"^9.11.1","eslint-plugin-unicorn":"^55.0.0","express":"^5.1.0","globals":"^15.10.0","h3":"^1.12.0","hono":"^4.6.20","husky":"^9.1.6","next":"^14.2.14","prettier":"3.3.3","tsup":"^8.3.0","typescript":"^5.7.2","typescript-eslint":"^8.18.0"},"dependencies":{"@upstash/qstash":"^2.11.0"},"directories":{"example":"examples"},"peerDependencies":{"zod":"^3.25.0 || ^4.0.0"}}
@@ -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/react-router.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/react-router.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/react-router.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/react-router.ts
8
8
  var telemetry = {
@@ -1,4 +1,4 @@
1
- import { d as WorkflowServeOptions, c as RouteFunction, I as InvokableWorkflow } from './types-B_E1VAK6.mjs';
1
+ import { d as WorkflowServeOptions, c as RouteFunction, I as InvokableWorkflow } from './types-CekOpKvz.mjs';
2
2
 
3
3
  type OmitOptionsInServeMany<TOptions> = Omit<TOptions, "env" | "url" | "schema" | "initialPayloadParser">;
4
4
  declare const serveManyBase: <THandler extends (...params: any[]) => any, TOptions extends OmitOptionsInServeMany<WorkflowServeOptions> = OmitOptionsInServeMany<WorkflowServeOptions>, TServeParams extends [routeFunction: RouteFunction<any, any>, options: TOptions] = [routeFunction: RouteFunction<any, any>, options: TOptions]>({ workflows, getUrl, serveMethod, options, }: {
@@ -1,4 +1,4 @@
1
- import { d as WorkflowServeOptions, c as RouteFunction, I as InvokableWorkflow } from './types-B_E1VAK6.js';
1
+ import { d as WorkflowServeOptions, c as RouteFunction, I as InvokableWorkflow } from './types-CekOpKvz.js';
2
2
 
3
3
  type OmitOptionsInServeMany<TOptions> = Omit<TOptions, "env" | "url" | "schema" | "initialPayloadParser">;
4
4
  declare const serveManyBase: <THandler extends (...params: any[]) => any, TOptions extends OmitOptionsInServeMany<WorkflowServeOptions> = OmitOptionsInServeMany<WorkflowServeOptions>, TServeParams extends [routeFunction: RouteFunction<any, any>, options: TOptions] = [routeFunction: RouteFunction<any, any>, options: TOptions]>({ workflows, getUrl, serveMethod, options, }: {
package/solidjs.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { APIEvent } from '@solidjs/start/server';
2
- import { c as RouteFunction, d as WorkflowServeOptions } from './types-B_E1VAK6.mjs';
2
+ import { c as RouteFunction, d as WorkflowServeOptions } from './types-CekOpKvz.mjs';
3
3
  import '@upstash/qstash';
4
4
  import 'zod';
5
5
 
package/solidjs.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { APIEvent } from '@solidjs/start/server';
2
- import { c as RouteFunction, d as WorkflowServeOptions } from './types-B_E1VAK6.js';
2
+ import { c as RouteFunction, d as WorkflowServeOptions } from './types-CekOpKvz.js';
3
3
  import '@upstash/qstash';
4
4
  import 'zod';
5
5