@upstash/workflow 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/express.js CHANGED
@@ -23692,7 +23692,9 @@ var require_express2 = __commonJS({
23692
23692
  // platforms/express.ts
23693
23693
  var express_exports = {};
23694
23694
  __export(express_exports, {
23695
- serve: () => serve
23695
+ createWorkflow: () => createWorkflow,
23696
+ serve: () => serve,
23697
+ serveMany: () => serveMany
23696
23698
  });
23697
23699
  module.exports = __toCommonJS(express_exports);
23698
23700
 
@@ -23702,12 +23704,13 @@ var WORKFLOW_INIT_HEADER = "Upstash-Workflow-Init";
23702
23704
  var WORKFLOW_URL_HEADER = "Upstash-Workflow-Url";
23703
23705
  var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
23704
23706
  var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
23707
+ var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
23705
23708
  var WORKFLOW_PROTOCOL_VERSION = "1";
23706
23709
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
23707
23710
  var DEFAULT_CONTENT_TYPE = "application/json";
23708
23711
  var NO_CONCURRENCY = 1;
23709
23712
  var DEFAULT_RETRIES = 3;
23710
- var VERSION = "v0.2.3";
23713
+ var VERSION = "v0.2.7";
23711
23714
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
23712
23715
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
23713
23716
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -23807,6 +23810,31 @@ var formatWorkflowError = (error) => {
23807
23810
  };
23808
23811
  };
23809
23812
 
23813
+ // src/utils.ts
23814
+ var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
23815
+ var NANOID_LENGTH = 21;
23816
+ function getRandomInt() {
23817
+ return Math.floor(Math.random() * NANOID_CHARS.length);
23818
+ }
23819
+ function nanoid() {
23820
+ return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
23821
+ }
23822
+ function getWorkflowRunId(id) {
23823
+ return `wfr_${id ?? nanoid()}`;
23824
+ }
23825
+ function decodeBase64(base64) {
23826
+ try {
23827
+ const binString = atob(base64);
23828
+ const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
23829
+ return new TextDecoder().decode(intArray);
23830
+ } catch (error) {
23831
+ console.warn(
23832
+ `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
23833
+ );
23834
+ return atob(base64);
23835
+ }
23836
+ }
23837
+
23810
23838
  // src/context/steps.ts
23811
23839
  var BaseLazyStep = class {
23812
23840
  stepName;
@@ -23911,8 +23939,9 @@ var LazyCallStep = class extends BaseLazyStep {
23911
23939
  headers;
23912
23940
  retries;
23913
23941
  timeout;
23942
+ flowControl;
23914
23943
  stepType = "Call";
23915
- constructor(stepName, url, method, body, headers, retries, timeout) {
23944
+ constructor(stepName, url, method, body, headers, retries, timeout, flowControl) {
23916
23945
  super(stepName);
23917
23946
  this.url = url;
23918
23947
  this.method = method;
@@ -23920,6 +23949,7 @@ var LazyCallStep = class extends BaseLazyStep {
23920
23949
  this.headers = headers;
23921
23950
  this.retries = retries;
23922
23951
  this.timeout = timeout;
23952
+ this.flowControl = flowControl;
23923
23953
  }
23924
23954
  getPlanStep(concurrent, targetStep) {
23925
23955
  return {
@@ -23987,6 +24017,49 @@ var LazyNotifyStep = class extends LazyFunctionStep {
23987
24017
  });
23988
24018
  }
23989
24019
  };
24020
+ var LazyInvokeStep = class extends BaseLazyStep {
24021
+ stepType = "Invoke";
24022
+ params;
24023
+ constructor(stepName, {
24024
+ workflow,
24025
+ body,
24026
+ headers = {},
24027
+ workflowRunId,
24028
+ retries,
24029
+ flowControl
24030
+ }) {
24031
+ super(stepName);
24032
+ this.params = {
24033
+ workflow,
24034
+ body,
24035
+ headers,
24036
+ workflowRunId: getWorkflowRunId(workflowRunId),
24037
+ retries,
24038
+ flowControl
24039
+ };
24040
+ }
24041
+ getPlanStep(concurrent, targetStep) {
24042
+ return {
24043
+ stepId: 0,
24044
+ stepName: this.stepName,
24045
+ stepType: this.stepType,
24046
+ concurrent,
24047
+ targetStep
24048
+ };
24049
+ }
24050
+ /**
24051
+ * won't be used as it's the server who will add the result step
24052
+ * in Invoke step.
24053
+ */
24054
+ getResultStep(concurrent, stepId) {
24055
+ return Promise.resolve({
24056
+ stepId,
24057
+ stepName: this.stepName,
24058
+ stepType: this.stepType,
24059
+ concurrent
24060
+ });
24061
+ }
24062
+ };
23990
24063
 
23991
24064
  // node_modules/neverthrow/dist/index.es.js
23992
24065
  var defaultErrorConfig = {
@@ -24411,7 +24484,8 @@ var StepTypes = [
24411
24484
  "SleepUntil",
24412
24485
  "Call",
24413
24486
  "Wait",
24414
- "Notify"
24487
+ "Notify",
24488
+ "Invoke"
24415
24489
  ];
24416
24490
 
24417
24491
  // src/workflow-requests.ts
@@ -24419,8 +24493,9 @@ var import_qstash3 = require("@upstash/qstash");
24419
24493
  var triggerFirstInvocation = async ({
24420
24494
  workflowContext,
24421
24495
  useJSONContent,
24422
- telemetry,
24423
- debug
24496
+ telemetry: telemetry2,
24497
+ debug,
24498
+ invokeCount
24424
24499
  }) => {
24425
24500
  const { headers } = getHeaders({
24426
24501
  initHeaderValue: "true",
@@ -24429,7 +24504,9 @@ var triggerFirstInvocation = async ({
24429
24504
  userHeaders: workflowContext.headers,
24430
24505
  failureUrl: workflowContext.failureUrl,
24431
24506
  retries: workflowContext.retries,
24432
- telemetry
24507
+ telemetry: telemetry2,
24508
+ invokeCount,
24509
+ flowControl: workflowContext.flowControl
24433
24510
  });
24434
24511
  if (workflowContext.headers.get("content-type")) {
24435
24512
  headers["content-type"] = workflowContext.headers.get("content-type");
@@ -24475,8 +24552,8 @@ var triggerRouteFunction = async ({
24475
24552
  debug
24476
24553
  }) => {
24477
24554
  try {
24478
- await onStep();
24479
- await onCleanup();
24555
+ const result = await onStep();
24556
+ await onCleanup(result);
24480
24557
  return ok("workflow-finished");
24481
24558
  } catch (error) {
24482
24559
  const error_ = error;
@@ -24497,14 +24574,15 @@ var triggerRouteFunction = async ({
24497
24574
  }
24498
24575
  }
24499
24576
  };
24500
- var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
24577
+ var triggerWorkflowDelete = async (workflowContext, result, debug, cancel = false) => {
24501
24578
  await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
24502
24579
  deletedWorkflowRunId: workflowContext.workflowRunId
24503
24580
  });
24504
24581
  await workflowContext.qstashClient.http.request({
24505
24582
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
24506
24583
  method: "DELETE",
24507
- parseResponseAsJson: false
24584
+ parseResponseAsJson: false,
24585
+ body: JSON.stringify(result)
24508
24586
  });
24509
24587
  await debug?.log(
24510
24588
  "SUBMIT",
@@ -24533,7 +24611,8 @@ var handleThirdPartyCallResult = async ({
24533
24611
  workflowUrl,
24534
24612
  failureUrl,
24535
24613
  retries,
24536
- telemetry,
24614
+ telemetry: telemetry2,
24615
+ flowControl,
24537
24616
  debug
24538
24617
  }) => {
24539
24618
  try {
@@ -24581,6 +24660,7 @@ ${atob(callbackMessage.body ?? "")}`
24581
24660
  const stepType = request.headers.get("Upstash-Workflow-StepType");
24582
24661
  const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
24583
24662
  const contentType = request.headers.get("Upstash-Workflow-ContentType");
24663
+ const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
24584
24664
  if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
24585
24665
  throw new Error(
24586
24666
  `Missing info in callback message source header: ${JSON.stringify({
@@ -24601,7 +24681,9 @@ ${atob(callbackMessage.body ?? "")}`
24601
24681
  userHeaders,
24602
24682
  failureUrl,
24603
24683
  retries,
24604
- telemetry
24684
+ telemetry: telemetry2,
24685
+ invokeCount: Number(invokeCount),
24686
+ flowControl
24605
24687
  });
24606
24688
  const callResponse = {
24607
24689
  status: callbackMessage.status,
@@ -24640,11 +24722,11 @@ ${atob(callbackMessage.body ?? "")}`
24640
24722
  );
24641
24723
  }
24642
24724
  };
24643
- var getTelemetryHeaders = (telemetry) => {
24725
+ var getTelemetryHeaders = (telemetry2) => {
24644
24726
  return {
24645
- [TELEMETRY_HEADER_SDK]: telemetry.sdk,
24646
- [TELEMETRY_HEADER_FRAMEWORK]: telemetry.framework,
24647
- [TELEMETRY_HEADER_RUNTIME]: telemetry.runtime ?? "unknown"
24727
+ [TELEMETRY_HEADER_SDK]: telemetry2.sdk,
24728
+ [TELEMETRY_HEADER_FRAMEWORK]: telemetry2.framework,
24729
+ [TELEMETRY_HEADER_RUNTIME]: telemetry2.runtime ?? "unknown"
24648
24730
  };
24649
24731
  };
24650
24732
  var getHeaders = ({
@@ -24657,15 +24739,24 @@ var getHeaders = ({
24657
24739
  step,
24658
24740
  callRetries,
24659
24741
  callTimeout,
24660
- telemetry
24742
+ telemetry: telemetry2,
24743
+ invokeCount,
24744
+ flowControl,
24745
+ callFlowControl
24661
24746
  }) => {
24747
+ const contentType = (userHeaders ? userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
24662
24748
  const baseHeaders = {
24663
24749
  [WORKFLOW_INIT_HEADER]: initHeaderValue,
24664
24750
  [WORKFLOW_ID_HEADER]: workflowRunId,
24665
24751
  [WORKFLOW_URL_HEADER]: workflowUrl,
24666
24752
  [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody",
24667
- ...telemetry ? getTelemetryHeaders(telemetry) : {}
24753
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
24754
+ "content-type": contentType,
24755
+ ...telemetry2 ? getTelemetryHeaders(telemetry2) : {}
24668
24756
  };
24757
+ if (invokeCount !== void 0 && !step?.callUrl) {
24758
+ baseHeaders[`Upstash-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`] = invokeCount.toString();
24759
+ }
24669
24760
  if (!step?.callUrl) {
24670
24761
  baseHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
24671
24762
  }
@@ -24682,6 +24773,11 @@ var getHeaders = ({
24682
24773
  if (retries !== void 0) {
24683
24774
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
24684
24775
  }
24776
+ if (flowControl) {
24777
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
24778
+ baseHeaders["Upstash-Failure-Callback-Flow-Control-Key"] = flowControlKey;
24779
+ baseHeaders["Upstash-Failure-Callback-Flow-Control-Value"] = flowControlValue;
24780
+ }
24685
24781
  if (!step?.callUrl) {
24686
24782
  baseHeaders["Upstash-Failure-Callback"] = failureUrl;
24687
24783
  }
@@ -24693,9 +24789,26 @@ var getHeaders = ({
24693
24789
  baseHeaders["Upstash-Callback-Retries"] = retries.toString();
24694
24790
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
24695
24791
  }
24696
- } else if (retries !== void 0) {
24697
- baseHeaders["Upstash-Retries"] = retries.toString();
24698
- baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
24792
+ if (callFlowControl) {
24793
+ const { flowControlKey, flowControlValue } = prepareFlowControl(callFlowControl);
24794
+ baseHeaders["Upstash-Flow-Control-Key"] = flowControlKey;
24795
+ baseHeaders["Upstash-Flow-Control-Value"] = flowControlValue;
24796
+ }
24797
+ if (flowControl) {
24798
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
24799
+ baseHeaders["Upstash-Callback-Flow-Control-Key"] = flowControlKey;
24800
+ baseHeaders["Upstash-Callback-Flow-Control-Value"] = flowControlValue;
24801
+ }
24802
+ } else {
24803
+ if (flowControl) {
24804
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
24805
+ baseHeaders["Upstash-Flow-Control-Key"] = flowControlKey;
24806
+ baseHeaders["Upstash-Flow-Control-Value"] = flowControlValue;
24807
+ }
24808
+ if (retries !== void 0) {
24809
+ baseHeaders["Upstash-Retries"] = retries.toString();
24810
+ baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
24811
+ }
24699
24812
  }
24700
24813
  if (userHeaders) {
24701
24814
  for (const header of userHeaders.keys()) {
@@ -24707,7 +24820,6 @@ var getHeaders = ({
24707
24820
  baseHeaders[`Upstash-Failure-Callback-Forward-${header}`] = userHeaders.get(header);
24708
24821
  }
24709
24822
  }
24710
- const contentType = (userHeaders ? userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
24711
24823
  if (step?.callHeaders) {
24712
24824
  const forwardedHeaders = Object.fromEntries(
24713
24825
  Object.entries(step.callHeaders).map(([header, value]) => [
@@ -24731,6 +24843,7 @@ var getHeaders = ({
24731
24843
  "Upstash-Callback-Forward-Upstash-Workflow-StepType": step.stepType,
24732
24844
  "Upstash-Callback-Forward-Upstash-Workflow-Concurrent": step.concurrent.toString(),
24733
24845
  "Upstash-Callback-Forward-Upstash-Workflow-ContentType": contentType,
24846
+ [`Upstash-Callback-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`]: (invokeCount ?? 0).toString(),
24734
24847
  "Upstash-Workflow-CallType": "toCallback"
24735
24848
  }
24736
24849
  };
@@ -24747,8 +24860,8 @@ var getHeaders = ({
24747
24860
  Object.entries(baseHeaders).map(([header, value]) => [header, [value]])
24748
24861
  ),
24749
24862
  // to include telemetry headers:
24750
- ...telemetry ? Object.fromEntries(
24751
- Object.entries(getTelemetryHeaders(telemetry)).map(([header, value]) => [
24863
+ ...telemetry2 ? Object.fromEntries(
24864
+ Object.entries(getTelemetryHeaders(telemetry2)).map(([header, value]) => [
24752
24865
  header,
24753
24866
  [value]
24754
24867
  ])
@@ -24757,8 +24870,7 @@ var getHeaders = ({
24757
24870
  "Upstash-Workflow-Runid": [workflowRunId],
24758
24871
  [WORKFLOW_INIT_HEADER]: ["false"],
24759
24872
  [WORKFLOW_URL_HEADER]: [workflowUrl],
24760
- "Upstash-Workflow-CallType": ["step"],
24761
- "Content-Type": [contentType]
24873
+ "Upstash-Workflow-CallType": ["step"]
24762
24874
  }
24763
24875
  };
24764
24876
  }
@@ -24789,9 +24901,159 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
24789
24901
  );
24790
24902
  }
24791
24903
  };
24904
+ var prepareFlowControl = (flowControl) => {
24905
+ const parallelism = flowControl.parallelism?.toString();
24906
+ const rate = flowControl.ratePerSecond?.toString();
24907
+ const controlValue = [
24908
+ parallelism ? `parallelism=${parallelism}` : void 0,
24909
+ rate ? `rate=${rate}` : void 0
24910
+ ].filter(Boolean);
24911
+ if (controlValue.length === 0) {
24912
+ throw new import_qstash3.QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
24913
+ }
24914
+ return {
24915
+ flowControlKey: flowControl.key,
24916
+ flowControlValue: controlValue.join(", ")
24917
+ };
24918
+ };
24792
24919
 
24793
24920
  // src/context/auto-executor.ts
24794
24921
  var import_qstash4 = require("@upstash/qstash");
24922
+
24923
+ // src/serve/serve-many.ts
24924
+ var getWorkflowId = (url) => {
24925
+ const components = url.split("/");
24926
+ const lastComponent = components[components.length - 1];
24927
+ return lastComponent.split("?")[0];
24928
+ };
24929
+ var serveManyBase = ({
24930
+ workflows,
24931
+ getUrl,
24932
+ serveMethod,
24933
+ options
24934
+ }) => {
24935
+ const workflowIds = [];
24936
+ const workflowMap = Object.fromEntries(
24937
+ Object.entries(workflows).map((workflow) => {
24938
+ const workflowId = workflow[0];
24939
+ if (workflowIds.includes(workflowId)) {
24940
+ throw new WorkflowError(
24941
+ `Duplicate workflow name found: '${workflowId}'. Please set different workflow names in serveMany.`
24942
+ );
24943
+ }
24944
+ if (workflowId.includes("/")) {
24945
+ throw new WorkflowError(
24946
+ `Invalid workflow name found: '${workflowId}'. Workflow name cannot contain '/'.`
24947
+ );
24948
+ }
24949
+ workflowIds.push(workflowId);
24950
+ workflow[1].workflowId = workflowId;
24951
+ workflow[1].options = {
24952
+ ...options,
24953
+ ...workflow[1].options
24954
+ };
24955
+ const params = [workflow[1].routeFunction, workflow[1].options];
24956
+ const handler = serveMethod(...params);
24957
+ return [workflowId, handler];
24958
+ })
24959
+ );
24960
+ return {
24961
+ handler: async (...params) => {
24962
+ const url = getUrl(...params);
24963
+ const pickedWorkflowId = getWorkflowId(url);
24964
+ if (!pickedWorkflowId) {
24965
+ return new Response(
24966
+ `Unexpected request in serveMany. workflowId not set. Please update the URL of your request.`,
24967
+ {
24968
+ status: 404
24969
+ }
24970
+ );
24971
+ }
24972
+ const workflow = workflowMap[pickedWorkflowId];
24973
+ if (!workflow) {
24974
+ return new Response(
24975
+ `No workflows in serveMany found for '${pickedWorkflowId}'. Please update the URL of your request.`,
24976
+ {
24977
+ status: 404
24978
+ }
24979
+ );
24980
+ }
24981
+ return await workflow(...params);
24982
+ }
24983
+ };
24984
+ };
24985
+ var invokeWorkflow = async ({
24986
+ settings,
24987
+ invokeStep,
24988
+ context,
24989
+ invokeCount,
24990
+ telemetry: telemetry2
24991
+ }) => {
24992
+ const {
24993
+ body,
24994
+ workflow,
24995
+ headers = {},
24996
+ workflowRunId = getWorkflowRunId(),
24997
+ retries,
24998
+ flowControl
24999
+ } = settings;
25000
+ const { workflowId } = workflow;
25001
+ const {
25002
+ retries: workflowRetries,
25003
+ failureFunction,
25004
+ failureUrl,
25005
+ useJSONContent,
25006
+ flowControl: workflowFlowControl
25007
+ } = workflow.options;
25008
+ if (!workflowId) {
25009
+ throw new WorkflowError("You can only invoke workflow which has a workflowId");
25010
+ }
25011
+ const { headers: invokerHeaders } = getHeaders({
25012
+ initHeaderValue: "false",
25013
+ workflowRunId: context.workflowRunId,
25014
+ workflowUrl: context.url,
25015
+ userHeaders: context.headers,
25016
+ failureUrl: context.failureUrl,
25017
+ retries: context.retries,
25018
+ telemetry: telemetry2,
25019
+ invokeCount,
25020
+ flowControl: context.flowControl
25021
+ });
25022
+ invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
25023
+ const newUrl = context.url.replace(/[^/]+$/, workflowId);
25024
+ const { headers: triggerHeaders } = getHeaders({
25025
+ initHeaderValue: "true",
25026
+ workflowRunId,
25027
+ workflowUrl: newUrl,
25028
+ userHeaders: new Headers(headers),
25029
+ retries: retries ?? workflowRetries,
25030
+ telemetry: telemetry2,
25031
+ failureUrl: failureFunction ? newUrl : failureUrl,
25032
+ invokeCount: invokeCount + 1,
25033
+ flowControl: flowControl ?? workflowFlowControl
25034
+ });
25035
+ triggerHeaders["Upstash-Workflow-Invoke"] = "true";
25036
+ if (useJSONContent) {
25037
+ triggerHeaders["content-type"] = "application/json";
25038
+ }
25039
+ const request = {
25040
+ body: JSON.stringify(body),
25041
+ headers: Object.fromEntries(
25042
+ Object.entries(invokerHeaders).map((pairs) => [pairs[0], [pairs[1]]])
25043
+ ),
25044
+ workflowRunId,
25045
+ workflowUrl: context.url,
25046
+ step: invokeStep
25047
+ };
25048
+ await context.qstashClient.publish({
25049
+ headers: triggerHeaders,
25050
+ method: "POST",
25051
+ body: JSON.stringify(request),
25052
+ url: newUrl
25053
+ });
25054
+ };
25055
+
25056
+ // src/context/auto-executor.ts
24795
25057
  var AutoExecutor = class _AutoExecutor {
24796
25058
  context;
24797
25059
  promises = /* @__PURE__ */ new WeakMap();
@@ -24800,14 +25062,16 @@ var AutoExecutor = class _AutoExecutor {
24800
25062
  nonPlanStepCount;
24801
25063
  steps;
24802
25064
  indexInCurrentList = 0;
25065
+ invokeCount;
24803
25066
  telemetry;
24804
25067
  stepCount = 0;
24805
25068
  planStepCount = 0;
24806
25069
  executingStep = false;
24807
- constructor(context, steps, telemetry, debug) {
25070
+ constructor(context, steps, telemetry2, invokeCount, debug) {
24808
25071
  this.context = context;
24809
25072
  this.steps = steps;
24810
- this.telemetry = telemetry;
25073
+ this.telemetry = telemetry2;
25074
+ this.invokeCount = invokeCount ?? 0;
24811
25075
  this.debug = debug;
24812
25076
  this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
24813
25077
  }
@@ -25030,7 +25294,9 @@ var AutoExecutor = class _AutoExecutor {
25030
25294
  step: waitStep,
25031
25295
  failureUrl: this.context.failureUrl,
25032
25296
  retries: this.context.retries,
25033
- telemetry: this.telemetry
25297
+ telemetry: this.telemetry,
25298
+ invokeCount: this.invokeCount,
25299
+ flowControl: this.context.flowControl
25034
25300
  });
25035
25301
  const waitBody = {
25036
25302
  url: this.context.url,
@@ -25053,7 +25319,19 @@ var AutoExecutor = class _AutoExecutor {
25053
25319
  method: "POST",
25054
25320
  parseResponseAsJson: false
25055
25321
  });
25056
- throw new WorkflowAbort(steps[0].stepName, steps[0]);
25322
+ throw new WorkflowAbort(waitStep.stepName, waitStep);
25323
+ }
25324
+ if (steps.length === 1 && lazySteps[0] instanceof LazyInvokeStep) {
25325
+ const invokeStep = steps[0];
25326
+ const lazyInvokeStep = lazySteps[0];
25327
+ await invokeWorkflow({
25328
+ settings: lazyInvokeStep.params,
25329
+ invokeStep,
25330
+ context: this.context,
25331
+ invokeCount: this.invokeCount,
25332
+ telemetry: this.telemetry
25333
+ });
25334
+ throw new WorkflowAbort(invokeStep.stepName, invokeStep);
25057
25335
  }
25058
25336
  const result = await this.context.qstashClient.batchJSON(
25059
25337
  steps.map((singleStep, index) => {
@@ -25068,11 +25346,14 @@ var AutoExecutor = class _AutoExecutor {
25068
25346
  retries: this.context.retries,
25069
25347
  callRetries: lazyStep instanceof LazyCallStep ? lazyStep.retries : void 0,
25070
25348
  callTimeout: lazyStep instanceof LazyCallStep ? lazyStep.timeout : void 0,
25071
- telemetry: this.telemetry
25349
+ telemetry: this.telemetry,
25350
+ invokeCount: this.invokeCount,
25351
+ flowControl: this.context.flowControl,
25352
+ callFlowControl: lazyStep instanceof LazyCallStep ? lazyStep.flowControl : void 0
25072
25353
  });
25073
25354
  const willWait = singleStep.concurrent === NO_CONCURRENCY || singleStep.stepId === 0;
25074
25355
  singleStep.out = JSON.stringify(singleStep.out);
25075
- return singleStep.callUrl ? (
25356
+ return singleStep.callUrl && lazyStep instanceof LazyCallStep ? (
25076
25357
  // if the step is a third party call, we call the third party
25077
25358
  // url (singleStep.callUrl) and pass information about the workflow
25078
25359
  // in the headers (handled in getHeaders). QStash makes the request
@@ -25729,6 +26010,11 @@ var WorkflowContext = class {
25729
26010
  * Number of retries
25730
26011
  */
25731
26012
  retries;
26013
+ /**
26014
+ * Settings for controlling the number of active requests
26015
+ * and number of requests per second with the same key.
26016
+ */
26017
+ flowControl;
25732
26018
  constructor({
25733
26019
  qstashClient,
25734
26020
  workflowRunId,
@@ -25740,7 +26026,9 @@ var WorkflowContext = class {
25740
26026
  initialPayload,
25741
26027
  env,
25742
26028
  retries,
25743
- telemetry
26029
+ telemetry: telemetry2,
26030
+ invokeCount,
26031
+ flowControl
25744
26032
  }) {
25745
26033
  this.qstashClient = qstashClient;
25746
26034
  this.workflowRunId = workflowRunId;
@@ -25751,7 +26039,8 @@ var WorkflowContext = class {
25751
26039
  this.requestPayload = initialPayload;
25752
26040
  this.env = env ?? {};
25753
26041
  this.retries = retries ?? DEFAULT_RETRIES;
25754
- this.executor = new AutoExecutor(this, this.steps, telemetry, debug);
26042
+ this.flowControl = flowControl;
26043
+ this.executor = new AutoExecutor(this, this.steps, telemetry2, invokeCount, debug);
25755
26044
  }
25756
26045
  /**
25757
26046
  * Executes a workflow step
@@ -25853,7 +26142,7 @@ var WorkflowContext = class {
25853
26142
  * }
25854
26143
  */
25855
26144
  async call(stepName, settings) {
25856
- const { url, method = "GET", body, headers = {}, retries = 0, timeout } = settings;
26145
+ const { url, method = "GET", body, headers = {}, retries = 0, timeout, flowControl } = settings;
25857
26146
  const result = await this.addStep(
25858
26147
  new LazyCallStep(
25859
26148
  stepName,
@@ -25862,7 +26151,8 @@ var WorkflowContext = class {
25862
26151
  body,
25863
26152
  headers,
25864
26153
  retries,
25865
- timeout
26154
+ timeout,
26155
+ flowControl
25866
26156
  )
25867
26157
  );
25868
26158
  if (typeof result === "string") {
@@ -25971,6 +26261,13 @@ var WorkflowContext = class {
25971
26261
  return result;
25972
26262
  }
25973
26263
  }
26264
+ async invoke(stepName, settings) {
26265
+ const result = await this.addStep(new LazyInvokeStep(stepName, settings));
26266
+ return {
26267
+ ...result,
26268
+ body: result.body ? JSON.parse(result.body) : void 0
26269
+ };
26270
+ }
25974
26271
  /**
25975
26272
  * Cancel the current workflow run
25976
26273
  *
@@ -26048,31 +26345,6 @@ var WorkflowLogger = class _WorkflowLogger {
26048
26345
  }
26049
26346
  };
26050
26347
 
26051
- // src/utils.ts
26052
- var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
26053
- var NANOID_LENGTH = 21;
26054
- function getRandomInt() {
26055
- return Math.floor(Math.random() * NANOID_CHARS.length);
26056
- }
26057
- function nanoid() {
26058
- return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
26059
- }
26060
- function getWorkflowRunId(id) {
26061
- return `wfr_${id ?? nanoid()}`;
26062
- }
26063
- function decodeBase64(base64) {
26064
- try {
26065
- const binString = atob(base64);
26066
- const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
26067
- return new TextDecoder().decode(intArray);
26068
- } catch (error) {
26069
- console.warn(
26070
- `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
26071
- );
26072
- return atob(base64);
26073
- }
26074
- }
26075
-
26076
26348
  // src/serve/authorization.ts
26077
26349
  var import_qstash8 = require("@upstash/qstash");
26078
26350
  var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
@@ -26117,7 +26389,8 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
26117
26389
  failureUrl: context.failureUrl,
26118
26390
  initialPayload: context.requestPayload,
26119
26391
  env: context.env,
26120
- retries: context.retries
26392
+ retries: context.retries,
26393
+ flowControl: context.flowControl
26121
26394
  });
26122
26395
  try {
26123
26396
  await routeFunction(disabledContext);
@@ -26270,7 +26543,7 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
26270
26543
  };
26271
26544
  }
26272
26545
  };
26273
- var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, debug) => {
26546
+ var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, flowControl, debug) => {
26274
26547
  if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
26275
26548
  return ok("not-failure-callback");
26276
26549
  }
@@ -26282,22 +26555,21 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
26282
26555
  );
26283
26556
  }
26284
26557
  try {
26285
- const { status, header, body, url, sourceHeader, sourceBody, workflowRunId } = JSON.parse(
26286
- requestPayload
26287
- );
26558
+ const { status, header, body, url, sourceBody, workflowRunId } = JSON.parse(requestPayload);
26288
26559
  const decodedBody = body ? decodeBase64(body) : "{}";
26289
26560
  const errorPayload = JSON.parse(decodedBody);
26290
26561
  const workflowContext = new WorkflowContext({
26291
26562
  qstashClient,
26292
26563
  workflowRunId,
26293
26564
  initialPayload: sourceBody ? initialPayloadParser(decodeBase64(sourceBody)) : void 0,
26294
- headers: recreateUserHeaders(new Headers(sourceHeader)),
26565
+ headers: recreateUserHeaders(request.headers),
26295
26566
  steps: [],
26296
26567
  url,
26297
26568
  failureUrl: url,
26298
26569
  debug,
26299
26570
  env,
26300
26571
  retries,
26572
+ flowControl,
26301
26573
  telemetry: void 0
26302
26574
  // not going to make requests in authentication check
26303
26575
  });
@@ -26410,7 +26682,7 @@ var determineUrls = async (request, url, baseUrl, failureFunction, failureUrl, d
26410
26682
  var AUTH_FAIL_MESSAGE = `Failed to authenticate Workflow request. If this is unexpected, see the caveat https://upstash.com/docs/workflow/basics/caveats#avoid-non-deterministic-code-outside-context-run`;
26411
26683
 
26412
26684
  // src/serve/index.ts
26413
- var serveBase = (routeFunction, telemetry, options) => {
26685
+ var serveBase = (routeFunction, telemetry2, options) => {
26414
26686
  const {
26415
26687
  qstashClient,
26416
26688
  onStepFinish,
@@ -26424,9 +26696,10 @@ var serveBase = (routeFunction, telemetry, options) => {
26424
26696
  env,
26425
26697
  retries,
26426
26698
  useJSONContent,
26427
- disableTelemetry
26699
+ disableTelemetry,
26700
+ flowControl
26428
26701
  } = processOptions(options);
26429
- telemetry = disableTelemetry ? void 0 : telemetry;
26702
+ telemetry2 = disableTelemetry ? void 0 : telemetry2;
26430
26703
  const debug = WorkflowLogger.getLogger(verbose);
26431
26704
  const handler = async (request) => {
26432
26705
  await debug?.log("INFO", "ENDPOINT_START");
@@ -26465,6 +26738,7 @@ var serveBase = (routeFunction, telemetry, options) => {
26465
26738
  failureFunction,
26466
26739
  env,
26467
26740
  retries,
26741
+ flowControl,
26468
26742
  debug
26469
26743
  );
26470
26744
  if (failureCheck.isErr()) {
@@ -26473,6 +26747,7 @@ var serveBase = (routeFunction, telemetry, options) => {
26473
26747
  await debug?.log("WARN", "RESPONSE_DEFAULT", "failureFunction executed");
26474
26748
  return onStepFinish(workflowRunId, "failure-callback");
26475
26749
  }
26750
+ const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
26476
26751
  const workflowContext = new WorkflowContext({
26477
26752
  qstashClient,
26478
26753
  workflowRunId,
@@ -26484,7 +26759,9 @@ var serveBase = (routeFunction, telemetry, options) => {
26484
26759
  debug,
26485
26760
  env,
26486
26761
  retries,
26487
- telemetry
26762
+ telemetry: telemetry2,
26763
+ invokeCount,
26764
+ flowControl
26488
26765
  });
26489
26766
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
26490
26767
  routeFunction,
@@ -26507,7 +26784,8 @@ var serveBase = (routeFunction, telemetry, options) => {
26507
26784
  workflowUrl,
26508
26785
  failureUrl: workflowFailureUrl,
26509
26786
  retries,
26510
- telemetry,
26787
+ flowControl,
26788
+ telemetry: telemetry2,
26511
26789
  debug
26512
26790
  });
26513
26791
  if (callReturnCheck.isErr()) {
@@ -26516,10 +26794,16 @@ var serveBase = (routeFunction, telemetry, options) => {
26516
26794
  });
26517
26795
  throw callReturnCheck.error;
26518
26796
  } else if (callReturnCheck.value === "continue-workflow") {
26519
- const result = isFirstInvocation ? await triggerFirstInvocation({ workflowContext, useJSONContent, telemetry, debug }) : await triggerRouteFunction({
26797
+ const result = isFirstInvocation ? await triggerFirstInvocation({
26798
+ workflowContext,
26799
+ useJSONContent,
26800
+ telemetry: telemetry2,
26801
+ debug,
26802
+ invokeCount
26803
+ }) : await triggerRouteFunction({
26520
26804
  onStep: async () => routeFunction(workflowContext),
26521
- onCleanup: async () => {
26522
- await triggerWorkflowDelete(workflowContext, debug);
26805
+ onCleanup: async (result2) => {
26806
+ await triggerWorkflowDelete(workflowContext, result2, debug);
26523
26807
  },
26524
26808
  onCancel: async () => {
26525
26809
  await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
@@ -26556,9 +26840,14 @@ var import_express = __toESM(require_express2());
26556
26840
  var isEmptyRequest = (req) => {
26557
26841
  return req.headers["content-type"] === "application/json" && req.headers["content-length"] === "0";
26558
26842
  };
26559
- function serve(routeFunction, options) {
26560
- const router = (0, import_express.Router)();
26561
- const handler = async (request_, res) => {
26843
+ var telemetry = {
26844
+ sdk: SDK_TELEMETRY,
26845
+ framework: "express",
26846
+ runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
26847
+ };
26848
+ function createExpressHandler(params) {
26849
+ const [routeFunction, options] = params;
26850
+ return async (request_, res) => {
26562
26851
  if (request_.method.toUpperCase() !== "POST") {
26563
26852
  res.status(405).json("Only POST requests are allowed in workflows");
26564
26853
  return;
@@ -26581,27 +26870,46 @@ function serve(routeFunction, options) {
26581
26870
  headers: new Headers(request_.headers),
26582
26871
  body: requestBody
26583
26872
  });
26584
- const { handler: serveHandler } = serveBase(
26585
- routeFunction,
26586
- {
26587
- sdk: SDK_TELEMETRY,
26588
- framework: "express",
26589
- runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
26590
- },
26591
- {
26592
- ...options,
26593
- useJSONContent: true
26594
- }
26595
- );
26873
+ const { handler: serveHandler } = serveBase(routeFunction, telemetry, {
26874
+ ...options,
26875
+ useJSONContent: true
26876
+ });
26596
26877
  const response = await serveHandler(webRequest);
26597
26878
  res.status(response.status).json(await response.json());
26598
26879
  };
26880
+ }
26881
+ function serve(routeFunction, options) {
26882
+ const router = (0, import_express.Router)();
26883
+ const handler = createExpressHandler([routeFunction, options]);
26599
26884
  router.all("*", handler);
26600
26885
  return router;
26601
26886
  }
26887
+ var createWorkflow = (...params) => {
26888
+ const [routeFunction, options = {}] = params;
26889
+ return {
26890
+ routeFunction,
26891
+ options,
26892
+ workflowId: void 0
26893
+ };
26894
+ };
26895
+ var serveMany = (workflows, options) => {
26896
+ const router = (0, import_express.Router)();
26897
+ const { handler } = serveManyBase({
26898
+ workflows,
26899
+ getUrl(...params) {
26900
+ return params[0].url;
26901
+ },
26902
+ serveMethod: (...params) => createExpressHandler(params),
26903
+ options
26904
+ });
26905
+ router.all("*", handler);
26906
+ return router;
26907
+ };
26602
26908
  // Annotate the CommonJS export names for ESM import in node:
26603
26909
  0 && (module.exports = {
26604
- serve
26910
+ createWorkflow,
26911
+ serve,
26912
+ serveMany
26605
26913
  });
26606
26914
  /*! Bundled license information:
26607
26915