@upstash/workflow 0.2.8 → 0.2.10-hono-generics

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/nextjs.js CHANGED
@@ -20,7 +20,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // platforms/nextjs.ts
21
21
  var nextjs_exports = {};
22
22
  __export(nextjs_exports, {
23
+ createWorkflow: () => createWorkflow,
24
+ createWorkflowPagesRouter: () => createWorkflowPagesRouter,
23
25
  serve: () => serve,
26
+ serveMany: () => serveMany,
27
+ serveManyPagesRouter: () => serveManyPagesRouter,
24
28
  servePagesRouter: () => servePagesRouter
25
29
  });
26
30
  module.exports = __toCommonJS(nextjs_exports);
@@ -84,12 +88,13 @@ var WORKFLOW_INIT_HEADER = "Upstash-Workflow-Init";
84
88
  var WORKFLOW_URL_HEADER = "Upstash-Workflow-Url";
85
89
  var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
86
90
  var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
91
+ var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
87
92
  var WORKFLOW_PROTOCOL_VERSION = "1";
88
93
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
89
94
  var DEFAULT_CONTENT_TYPE = "application/json";
90
95
  var NO_CONCURRENCY = 1;
91
96
  var DEFAULT_RETRIES = 3;
92
- var VERSION = "v0.2.3";
97
+ var VERSION = "v0.2.7";
93
98
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
94
99
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
95
100
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -136,6 +141,31 @@ var formatWorkflowError = (error) => {
136
141
  };
137
142
  };
138
143
 
144
+ // src/utils.ts
145
+ var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
146
+ var NANOID_LENGTH = 21;
147
+ function getRandomInt() {
148
+ return Math.floor(Math.random() * NANOID_CHARS.length);
149
+ }
150
+ function nanoid() {
151
+ return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
152
+ }
153
+ function getWorkflowRunId(id) {
154
+ return `wfr_${id ?? nanoid()}`;
155
+ }
156
+ function decodeBase64(base64) {
157
+ try {
158
+ const binString = atob(base64);
159
+ const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
160
+ return new TextDecoder().decode(intArray);
161
+ } catch (error) {
162
+ console.warn(
163
+ `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
164
+ );
165
+ return atob(base64);
166
+ }
167
+ }
168
+
139
169
  // src/context/steps.ts
140
170
  var BaseLazyStep = class {
141
171
  stepName;
@@ -240,8 +270,9 @@ var LazyCallStep = class extends BaseLazyStep {
240
270
  headers;
241
271
  retries;
242
272
  timeout;
273
+ flowControl;
243
274
  stepType = "Call";
244
- constructor(stepName, url, method, body, headers, retries, timeout) {
275
+ constructor(stepName, url, method, body, headers, retries, timeout, flowControl) {
245
276
  super(stepName);
246
277
  this.url = url;
247
278
  this.method = method;
@@ -249,6 +280,7 @@ var LazyCallStep = class extends BaseLazyStep {
249
280
  this.headers = headers;
250
281
  this.retries = retries;
251
282
  this.timeout = timeout;
283
+ this.flowControl = flowControl;
252
284
  }
253
285
  getPlanStep(concurrent, targetStep) {
254
286
  return {
@@ -316,6 +348,49 @@ var LazyNotifyStep = class extends LazyFunctionStep {
316
348
  });
317
349
  }
318
350
  };
351
+ var LazyInvokeStep = class extends BaseLazyStep {
352
+ stepType = "Invoke";
353
+ params;
354
+ constructor(stepName, {
355
+ workflow,
356
+ body,
357
+ headers = {},
358
+ workflowRunId,
359
+ retries,
360
+ flowControl
361
+ }) {
362
+ super(stepName);
363
+ this.params = {
364
+ workflow,
365
+ body,
366
+ headers,
367
+ workflowRunId: getWorkflowRunId(workflowRunId),
368
+ retries,
369
+ flowControl
370
+ };
371
+ }
372
+ getPlanStep(concurrent, targetStep) {
373
+ return {
374
+ stepId: 0,
375
+ stepName: this.stepName,
376
+ stepType: this.stepType,
377
+ concurrent,
378
+ targetStep
379
+ };
380
+ }
381
+ /**
382
+ * won't be used as it's the server who will add the result step
383
+ * in Invoke step.
384
+ */
385
+ getResultStep(concurrent, stepId) {
386
+ return Promise.resolve({
387
+ stepId,
388
+ stepName: this.stepName,
389
+ stepType: this.stepType,
390
+ concurrent
391
+ });
392
+ }
393
+ };
319
394
 
320
395
  // node_modules/neverthrow/dist/index.es.js
321
396
  var defaultErrorConfig = {
@@ -740,7 +815,8 @@ var StepTypes = [
740
815
  "SleepUntil",
741
816
  "Call",
742
817
  "Wait",
743
- "Notify"
818
+ "Notify",
819
+ "Invoke"
744
820
  ];
745
821
 
746
822
  // src/workflow-requests.ts
@@ -749,7 +825,8 @@ var triggerFirstInvocation = async ({
749
825
  workflowContext,
750
826
  useJSONContent,
751
827
  telemetry,
752
- debug
828
+ debug,
829
+ invokeCount
753
830
  }) => {
754
831
  const { headers } = getHeaders({
755
832
  initHeaderValue: "true",
@@ -758,7 +835,9 @@ var triggerFirstInvocation = async ({
758
835
  userHeaders: workflowContext.headers,
759
836
  failureUrl: workflowContext.failureUrl,
760
837
  retries: workflowContext.retries,
761
- telemetry
838
+ telemetry,
839
+ invokeCount,
840
+ flowControl: workflowContext.flowControl
762
841
  });
763
842
  if (workflowContext.headers.get("content-type")) {
764
843
  headers["content-type"] = workflowContext.headers.get("content-type");
@@ -804,8 +883,8 @@ var triggerRouteFunction = async ({
804
883
  debug
805
884
  }) => {
806
885
  try {
807
- await onStep();
808
- await onCleanup();
886
+ const result = await onStep();
887
+ await onCleanup(result);
809
888
  return ok("workflow-finished");
810
889
  } catch (error) {
811
890
  const error_ = error;
@@ -826,14 +905,15 @@ var triggerRouteFunction = async ({
826
905
  }
827
906
  }
828
907
  };
829
- var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
908
+ var triggerWorkflowDelete = async (workflowContext, result, debug, cancel = false) => {
830
909
  await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
831
910
  deletedWorkflowRunId: workflowContext.workflowRunId
832
911
  });
833
912
  await workflowContext.qstashClient.http.request({
834
913
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
835
914
  method: "DELETE",
836
- parseResponseAsJson: false
915
+ parseResponseAsJson: false,
916
+ body: JSON.stringify(result)
837
917
  });
838
918
  await debug?.log(
839
919
  "SUBMIT",
@@ -863,6 +943,7 @@ var handleThirdPartyCallResult = async ({
863
943
  failureUrl,
864
944
  retries,
865
945
  telemetry,
946
+ flowControl,
866
947
  debug
867
948
  }) => {
868
949
  try {
@@ -910,6 +991,7 @@ ${atob(callbackMessage.body ?? "")}`
910
991
  const stepType = request.headers.get("Upstash-Workflow-StepType");
911
992
  const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
912
993
  const contentType = request.headers.get("Upstash-Workflow-ContentType");
994
+ const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
913
995
  if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
914
996
  throw new Error(
915
997
  `Missing info in callback message source header: ${JSON.stringify({
@@ -930,7 +1012,9 @@ ${atob(callbackMessage.body ?? "")}`
930
1012
  userHeaders,
931
1013
  failureUrl,
932
1014
  retries,
933
- telemetry
1015
+ telemetry,
1016
+ invokeCount: Number(invokeCount),
1017
+ flowControl
934
1018
  });
935
1019
  const callResponse = {
936
1020
  status: callbackMessage.status,
@@ -986,15 +1070,24 @@ var getHeaders = ({
986
1070
  step,
987
1071
  callRetries,
988
1072
  callTimeout,
989
- telemetry
1073
+ telemetry,
1074
+ invokeCount,
1075
+ flowControl,
1076
+ callFlowControl
990
1077
  }) => {
1078
+ const contentType = (userHeaders ? userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
991
1079
  const baseHeaders = {
992
1080
  [WORKFLOW_INIT_HEADER]: initHeaderValue,
993
1081
  [WORKFLOW_ID_HEADER]: workflowRunId,
994
1082
  [WORKFLOW_URL_HEADER]: workflowUrl,
995
1083
  [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody",
1084
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1085
+ "content-type": contentType,
996
1086
  ...telemetry ? getTelemetryHeaders(telemetry) : {}
997
1087
  };
1088
+ if (invokeCount !== void 0 && !step?.callUrl) {
1089
+ baseHeaders[`Upstash-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`] = invokeCount.toString();
1090
+ }
998
1091
  if (!step?.callUrl) {
999
1092
  baseHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
1000
1093
  }
@@ -1011,6 +1104,11 @@ var getHeaders = ({
1011
1104
  if (retries !== void 0) {
1012
1105
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1013
1106
  }
1107
+ if (flowControl) {
1108
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1109
+ baseHeaders["Upstash-Failure-Callback-Flow-Control-Key"] = flowControlKey;
1110
+ baseHeaders["Upstash-Failure-Callback-Flow-Control-Value"] = flowControlValue;
1111
+ }
1014
1112
  if (!step?.callUrl) {
1015
1113
  baseHeaders["Upstash-Failure-Callback"] = failureUrl;
1016
1114
  }
@@ -1022,9 +1120,26 @@ var getHeaders = ({
1022
1120
  baseHeaders["Upstash-Callback-Retries"] = retries.toString();
1023
1121
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1024
1122
  }
1025
- } else if (retries !== void 0) {
1026
- baseHeaders["Upstash-Retries"] = retries.toString();
1027
- baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1123
+ if (callFlowControl) {
1124
+ const { flowControlKey, flowControlValue } = prepareFlowControl(callFlowControl);
1125
+ baseHeaders["Upstash-Flow-Control-Key"] = flowControlKey;
1126
+ baseHeaders["Upstash-Flow-Control-Value"] = flowControlValue;
1127
+ }
1128
+ if (flowControl) {
1129
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1130
+ baseHeaders["Upstash-Callback-Flow-Control-Key"] = flowControlKey;
1131
+ baseHeaders["Upstash-Callback-Flow-Control-Value"] = flowControlValue;
1132
+ }
1133
+ } else {
1134
+ if (flowControl) {
1135
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1136
+ baseHeaders["Upstash-Flow-Control-Key"] = flowControlKey;
1137
+ baseHeaders["Upstash-Flow-Control-Value"] = flowControlValue;
1138
+ }
1139
+ if (retries !== void 0) {
1140
+ baseHeaders["Upstash-Retries"] = retries.toString();
1141
+ baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1142
+ }
1028
1143
  }
1029
1144
  if (userHeaders) {
1030
1145
  for (const header of userHeaders.keys()) {
@@ -1036,7 +1151,6 @@ var getHeaders = ({
1036
1151
  baseHeaders[`Upstash-Failure-Callback-Forward-${header}`] = userHeaders.get(header);
1037
1152
  }
1038
1153
  }
1039
- const contentType = (userHeaders ? userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
1040
1154
  if (step?.callHeaders) {
1041
1155
  const forwardedHeaders = Object.fromEntries(
1042
1156
  Object.entries(step.callHeaders).map(([header, value]) => [
@@ -1060,6 +1174,7 @@ var getHeaders = ({
1060
1174
  "Upstash-Callback-Forward-Upstash-Workflow-StepType": step.stepType,
1061
1175
  "Upstash-Callback-Forward-Upstash-Workflow-Concurrent": step.concurrent.toString(),
1062
1176
  "Upstash-Callback-Forward-Upstash-Workflow-ContentType": contentType,
1177
+ [`Upstash-Callback-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`]: (invokeCount ?? 0).toString(),
1063
1178
  "Upstash-Workflow-CallType": "toCallback"
1064
1179
  }
1065
1180
  };
@@ -1086,8 +1201,7 @@ var getHeaders = ({
1086
1201
  "Upstash-Workflow-Runid": [workflowRunId],
1087
1202
  [WORKFLOW_INIT_HEADER]: ["false"],
1088
1203
  [WORKFLOW_URL_HEADER]: [workflowUrl],
1089
- "Upstash-Workflow-CallType": ["step"],
1090
- "Content-Type": [contentType]
1204
+ "Upstash-Workflow-CallType": ["step"]
1091
1205
  }
1092
1206
  };
1093
1207
  }
@@ -1118,9 +1232,159 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
1118
1232
  );
1119
1233
  }
1120
1234
  };
1235
+ var prepareFlowControl = (flowControl) => {
1236
+ const parallelism = flowControl.parallelism?.toString();
1237
+ const rate = flowControl.ratePerSecond?.toString();
1238
+ const controlValue = [
1239
+ parallelism ? `parallelism=${parallelism}` : void 0,
1240
+ rate ? `rate=${rate}` : void 0
1241
+ ].filter(Boolean);
1242
+ if (controlValue.length === 0) {
1243
+ throw new import_qstash3.QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
1244
+ }
1245
+ return {
1246
+ flowControlKey: flowControl.key,
1247
+ flowControlValue: controlValue.join(", ")
1248
+ };
1249
+ };
1121
1250
 
1122
1251
  // src/context/auto-executor.ts
1123
1252
  var import_qstash4 = require("@upstash/qstash");
1253
+
1254
+ // src/serve/serve-many.ts
1255
+ var getWorkflowId = (url) => {
1256
+ const components = url.split("/");
1257
+ const lastComponent = components[components.length - 1];
1258
+ return lastComponent.split("?")[0];
1259
+ };
1260
+ var serveManyBase = ({
1261
+ workflows,
1262
+ getUrl,
1263
+ serveMethod,
1264
+ options
1265
+ }) => {
1266
+ const workflowIds = [];
1267
+ const workflowMap = Object.fromEntries(
1268
+ Object.entries(workflows).map((workflow) => {
1269
+ const workflowId = workflow[0];
1270
+ if (workflowIds.includes(workflowId)) {
1271
+ throw new WorkflowError(
1272
+ `Duplicate workflow name found: '${workflowId}'. Please set different workflow names in serveMany.`
1273
+ );
1274
+ }
1275
+ if (workflowId.includes("/")) {
1276
+ throw new WorkflowError(
1277
+ `Invalid workflow name found: '${workflowId}'. Workflow name cannot contain '/'.`
1278
+ );
1279
+ }
1280
+ workflowIds.push(workflowId);
1281
+ workflow[1].workflowId = workflowId;
1282
+ workflow[1].options = {
1283
+ ...options,
1284
+ ...workflow[1].options
1285
+ };
1286
+ const params = [workflow[1].routeFunction, workflow[1].options];
1287
+ const handler = serveMethod(...params);
1288
+ return [workflowId, handler];
1289
+ })
1290
+ );
1291
+ return {
1292
+ handler: async (...params) => {
1293
+ const url = getUrl(...params);
1294
+ const pickedWorkflowId = getWorkflowId(url);
1295
+ if (!pickedWorkflowId) {
1296
+ return new Response(
1297
+ `Unexpected request in serveMany. workflowId not set. Please update the URL of your request.`,
1298
+ {
1299
+ status: 404
1300
+ }
1301
+ );
1302
+ }
1303
+ const workflow = workflowMap[pickedWorkflowId];
1304
+ if (!workflow) {
1305
+ return new Response(
1306
+ `No workflows in serveMany found for '${pickedWorkflowId}'. Please update the URL of your request.`,
1307
+ {
1308
+ status: 404
1309
+ }
1310
+ );
1311
+ }
1312
+ return await workflow(...params);
1313
+ }
1314
+ };
1315
+ };
1316
+ var invokeWorkflow = async ({
1317
+ settings,
1318
+ invokeStep,
1319
+ context,
1320
+ invokeCount,
1321
+ telemetry
1322
+ }) => {
1323
+ const {
1324
+ body,
1325
+ workflow,
1326
+ headers = {},
1327
+ workflowRunId = getWorkflowRunId(),
1328
+ retries,
1329
+ flowControl
1330
+ } = settings;
1331
+ const { workflowId } = workflow;
1332
+ const {
1333
+ retries: workflowRetries,
1334
+ failureFunction,
1335
+ failureUrl,
1336
+ useJSONContent,
1337
+ flowControl: workflowFlowControl
1338
+ } = workflow.options;
1339
+ if (!workflowId) {
1340
+ throw new WorkflowError("You can only invoke workflow which has a workflowId");
1341
+ }
1342
+ const { headers: invokerHeaders } = getHeaders({
1343
+ initHeaderValue: "false",
1344
+ workflowRunId: context.workflowRunId,
1345
+ workflowUrl: context.url,
1346
+ userHeaders: context.headers,
1347
+ failureUrl: context.failureUrl,
1348
+ retries: context.retries,
1349
+ telemetry,
1350
+ invokeCount,
1351
+ flowControl: context.flowControl
1352
+ });
1353
+ invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
1354
+ const newUrl = context.url.replace(/[^/]+$/, workflowId);
1355
+ const { headers: triggerHeaders } = getHeaders({
1356
+ initHeaderValue: "true",
1357
+ workflowRunId,
1358
+ workflowUrl: newUrl,
1359
+ userHeaders: new Headers(headers),
1360
+ retries: retries ?? workflowRetries,
1361
+ telemetry,
1362
+ failureUrl: failureFunction ? newUrl : failureUrl,
1363
+ invokeCount: invokeCount + 1,
1364
+ flowControl: flowControl ?? workflowFlowControl
1365
+ });
1366
+ triggerHeaders["Upstash-Workflow-Invoke"] = "true";
1367
+ if (useJSONContent) {
1368
+ triggerHeaders["content-type"] = "application/json";
1369
+ }
1370
+ const request = {
1371
+ body: JSON.stringify(body),
1372
+ headers: Object.fromEntries(
1373
+ Object.entries(invokerHeaders).map((pairs) => [pairs[0], [pairs[1]]])
1374
+ ),
1375
+ workflowRunId,
1376
+ workflowUrl: context.url,
1377
+ step: invokeStep
1378
+ };
1379
+ await context.qstashClient.publish({
1380
+ headers: triggerHeaders,
1381
+ method: "POST",
1382
+ body: JSON.stringify(request),
1383
+ url: newUrl
1384
+ });
1385
+ };
1386
+
1387
+ // src/context/auto-executor.ts
1124
1388
  var AutoExecutor = class _AutoExecutor {
1125
1389
  context;
1126
1390
  promises = /* @__PURE__ */ new WeakMap();
@@ -1129,14 +1393,16 @@ var AutoExecutor = class _AutoExecutor {
1129
1393
  nonPlanStepCount;
1130
1394
  steps;
1131
1395
  indexInCurrentList = 0;
1396
+ invokeCount;
1132
1397
  telemetry;
1133
1398
  stepCount = 0;
1134
1399
  planStepCount = 0;
1135
1400
  executingStep = false;
1136
- constructor(context, steps, telemetry, debug) {
1401
+ constructor(context, steps, telemetry, invokeCount, debug) {
1137
1402
  this.context = context;
1138
1403
  this.steps = steps;
1139
1404
  this.telemetry = telemetry;
1405
+ this.invokeCount = invokeCount ?? 0;
1140
1406
  this.debug = debug;
1141
1407
  this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
1142
1408
  }
@@ -1359,7 +1625,9 @@ var AutoExecutor = class _AutoExecutor {
1359
1625
  step: waitStep,
1360
1626
  failureUrl: this.context.failureUrl,
1361
1627
  retries: this.context.retries,
1362
- telemetry: this.telemetry
1628
+ telemetry: this.telemetry,
1629
+ invokeCount: this.invokeCount,
1630
+ flowControl: this.context.flowControl
1363
1631
  });
1364
1632
  const waitBody = {
1365
1633
  url: this.context.url,
@@ -1382,7 +1650,19 @@ var AutoExecutor = class _AutoExecutor {
1382
1650
  method: "POST",
1383
1651
  parseResponseAsJson: false
1384
1652
  });
1385
- throw new WorkflowAbort(steps[0].stepName, steps[0]);
1653
+ throw new WorkflowAbort(waitStep.stepName, waitStep);
1654
+ }
1655
+ if (steps.length === 1 && lazySteps[0] instanceof LazyInvokeStep) {
1656
+ const invokeStep = steps[0];
1657
+ const lazyInvokeStep = lazySteps[0];
1658
+ await invokeWorkflow({
1659
+ settings: lazyInvokeStep.params,
1660
+ invokeStep,
1661
+ context: this.context,
1662
+ invokeCount: this.invokeCount,
1663
+ telemetry: this.telemetry
1664
+ });
1665
+ throw new WorkflowAbort(invokeStep.stepName, invokeStep);
1386
1666
  }
1387
1667
  const result = await this.context.qstashClient.batchJSON(
1388
1668
  steps.map((singleStep, index) => {
@@ -1397,11 +1677,14 @@ var AutoExecutor = class _AutoExecutor {
1397
1677
  retries: this.context.retries,
1398
1678
  callRetries: lazyStep instanceof LazyCallStep ? lazyStep.retries : void 0,
1399
1679
  callTimeout: lazyStep instanceof LazyCallStep ? lazyStep.timeout : void 0,
1400
- telemetry: this.telemetry
1680
+ telemetry: this.telemetry,
1681
+ invokeCount: this.invokeCount,
1682
+ flowControl: this.context.flowControl,
1683
+ callFlowControl: lazyStep instanceof LazyCallStep ? lazyStep.flowControl : void 0
1401
1684
  });
1402
1685
  const willWait = singleStep.concurrent === NO_CONCURRENCY || singleStep.stepId === 0;
1403
1686
  singleStep.out = JSON.stringify(singleStep.out);
1404
- return singleStep.callUrl ? (
1687
+ return singleStep.callUrl && lazyStep instanceof LazyCallStep ? (
1405
1688
  // if the step is a third party call, we call the third party
1406
1689
  // url (singleStep.callUrl) and pass information about the workflow
1407
1690
  // in the headers (handled in getHeaders). QStash makes the request
@@ -2058,6 +2341,11 @@ var WorkflowContext = class {
2058
2341
  * Number of retries
2059
2342
  */
2060
2343
  retries;
2344
+ /**
2345
+ * Settings for controlling the number of active requests
2346
+ * and number of requests per second with the same key.
2347
+ */
2348
+ flowControl;
2061
2349
  constructor({
2062
2350
  qstashClient,
2063
2351
  workflowRunId,
@@ -2069,7 +2357,9 @@ var WorkflowContext = class {
2069
2357
  initialPayload,
2070
2358
  env,
2071
2359
  retries,
2072
- telemetry
2360
+ telemetry,
2361
+ invokeCount,
2362
+ flowControl
2073
2363
  }) {
2074
2364
  this.qstashClient = qstashClient;
2075
2365
  this.workflowRunId = workflowRunId;
@@ -2080,7 +2370,8 @@ var WorkflowContext = class {
2080
2370
  this.requestPayload = initialPayload;
2081
2371
  this.env = env ?? {};
2082
2372
  this.retries = retries ?? DEFAULT_RETRIES;
2083
- this.executor = new AutoExecutor(this, this.steps, telemetry, debug);
2373
+ this.flowControl = flowControl;
2374
+ this.executor = new AutoExecutor(this, this.steps, telemetry, invokeCount, debug);
2084
2375
  }
2085
2376
  /**
2086
2377
  * Executes a workflow step
@@ -2182,7 +2473,7 @@ var WorkflowContext = class {
2182
2473
  * }
2183
2474
  */
2184
2475
  async call(stepName, settings) {
2185
- const { url, method = "GET", body, headers = {}, retries = 0, timeout } = settings;
2476
+ const { url, method = "GET", body, headers = {}, retries = 0, timeout, flowControl } = settings;
2186
2477
  const result = await this.addStep(
2187
2478
  new LazyCallStep(
2188
2479
  stepName,
@@ -2191,7 +2482,8 @@ var WorkflowContext = class {
2191
2482
  body,
2192
2483
  headers,
2193
2484
  retries,
2194
- timeout
2485
+ timeout,
2486
+ flowControl
2195
2487
  )
2196
2488
  );
2197
2489
  if (typeof result === "string") {
@@ -2300,6 +2592,13 @@ var WorkflowContext = class {
2300
2592
  return result;
2301
2593
  }
2302
2594
  }
2595
+ async invoke(stepName, settings) {
2596
+ const result = await this.addStep(new LazyInvokeStep(stepName, settings));
2597
+ return {
2598
+ ...result,
2599
+ body: result.body ? JSON.parse(result.body) : void 0
2600
+ };
2601
+ }
2303
2602
  /**
2304
2603
  * Cancel the current workflow run
2305
2604
  *
@@ -2377,31 +2676,6 @@ var WorkflowLogger = class _WorkflowLogger {
2377
2676
  }
2378
2677
  };
2379
2678
 
2380
- // src/utils.ts
2381
- var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
2382
- var NANOID_LENGTH = 21;
2383
- function getRandomInt() {
2384
- return Math.floor(Math.random() * NANOID_CHARS.length);
2385
- }
2386
- function nanoid() {
2387
- return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
2388
- }
2389
- function getWorkflowRunId(id) {
2390
- return `wfr_${id ?? nanoid()}`;
2391
- }
2392
- function decodeBase64(base64) {
2393
- try {
2394
- const binString = atob(base64);
2395
- const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
2396
- return new TextDecoder().decode(intArray);
2397
- } catch (error) {
2398
- console.warn(
2399
- `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
2400
- );
2401
- return atob(base64);
2402
- }
2403
- }
2404
-
2405
2679
  // src/serve/authorization.ts
2406
2680
  var import_qstash8 = require("@upstash/qstash");
2407
2681
  var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
@@ -2446,7 +2720,8 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
2446
2720
  failureUrl: context.failureUrl,
2447
2721
  initialPayload: context.requestPayload,
2448
2722
  env: context.env,
2449
- retries: context.retries
2723
+ retries: context.retries,
2724
+ flowControl: context.flowControl
2450
2725
  });
2451
2726
  try {
2452
2727
  await routeFunction(disabledContext);
@@ -2599,7 +2874,7 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
2599
2874
  };
2600
2875
  }
2601
2876
  };
2602
- var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, debug) => {
2877
+ var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, flowControl, debug) => {
2603
2878
  if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
2604
2879
  return ok("not-failure-callback");
2605
2880
  }
@@ -2611,22 +2886,21 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
2611
2886
  );
2612
2887
  }
2613
2888
  try {
2614
- const { status, header, body, url, sourceHeader, sourceBody, workflowRunId } = JSON.parse(
2615
- requestPayload
2616
- );
2889
+ const { status, header, body, url, sourceBody, workflowRunId } = JSON.parse(requestPayload);
2617
2890
  const decodedBody = body ? decodeBase64(body) : "{}";
2618
2891
  const errorPayload = JSON.parse(decodedBody);
2619
2892
  const workflowContext = new WorkflowContext({
2620
2893
  qstashClient,
2621
2894
  workflowRunId,
2622
2895
  initialPayload: sourceBody ? initialPayloadParser(decodeBase64(sourceBody)) : void 0,
2623
- headers: recreateUserHeaders(new Headers(sourceHeader)),
2896
+ headers: recreateUserHeaders(request.headers),
2624
2897
  steps: [],
2625
2898
  url,
2626
2899
  failureUrl: url,
2627
2900
  debug,
2628
2901
  env,
2629
2902
  retries,
2903
+ flowControl,
2630
2904
  telemetry: void 0
2631
2905
  // not going to make requests in authentication check
2632
2906
  });
@@ -2753,7 +3027,8 @@ var serveBase = (routeFunction, telemetry, options) => {
2753
3027
  env,
2754
3028
  retries,
2755
3029
  useJSONContent,
2756
- disableTelemetry
3030
+ disableTelemetry,
3031
+ flowControl
2757
3032
  } = processOptions(options);
2758
3033
  telemetry = disableTelemetry ? void 0 : telemetry;
2759
3034
  const debug = WorkflowLogger.getLogger(verbose);
@@ -2794,6 +3069,7 @@ var serveBase = (routeFunction, telemetry, options) => {
2794
3069
  failureFunction,
2795
3070
  env,
2796
3071
  retries,
3072
+ flowControl,
2797
3073
  debug
2798
3074
  );
2799
3075
  if (failureCheck.isErr()) {
@@ -2802,6 +3078,7 @@ var serveBase = (routeFunction, telemetry, options) => {
2802
3078
  await debug?.log("WARN", "RESPONSE_DEFAULT", "failureFunction executed");
2803
3079
  return onStepFinish(workflowRunId, "failure-callback");
2804
3080
  }
3081
+ const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
2805
3082
  const workflowContext = new WorkflowContext({
2806
3083
  qstashClient,
2807
3084
  workflowRunId,
@@ -2813,7 +3090,9 @@ var serveBase = (routeFunction, telemetry, options) => {
2813
3090
  debug,
2814
3091
  env,
2815
3092
  retries,
2816
- telemetry
3093
+ telemetry,
3094
+ invokeCount,
3095
+ flowControl
2817
3096
  });
2818
3097
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
2819
3098
  routeFunction,
@@ -2836,6 +3115,7 @@ var serveBase = (routeFunction, telemetry, options) => {
2836
3115
  workflowUrl,
2837
3116
  failureUrl: workflowFailureUrl,
2838
3117
  retries,
3118
+ flowControl,
2839
3119
  telemetry,
2840
3120
  debug
2841
3121
  });
@@ -2845,10 +3125,16 @@ var serveBase = (routeFunction, telemetry, options) => {
2845
3125
  });
2846
3126
  throw callReturnCheck.error;
2847
3127
  } else if (callReturnCheck.value === "continue-workflow") {
2848
- const result = isFirstInvocation ? await triggerFirstInvocation({ workflowContext, useJSONContent, telemetry, debug }) : await triggerRouteFunction({
3128
+ const result = isFirstInvocation ? await triggerFirstInvocation({
3129
+ workflowContext,
3130
+ useJSONContent,
3131
+ telemetry,
3132
+ debug,
3133
+ invokeCount
3134
+ }) : await triggerRouteFunction({
2849
3135
  onStep: async () => routeFunction(workflowContext),
2850
- onCleanup: async () => {
2851
- await triggerWorkflowDelete(workflowContext, debug);
3136
+ onCleanup: async (result2) => {
3137
+ await triggerWorkflowDelete(workflowContext, result2, debug);
2852
3138
  },
2853
3139
  onCancel: async () => {
2854
3140
  await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
@@ -2881,14 +3167,20 @@ var serveBase = (routeFunction, telemetry, options) => {
2881
3167
  };
2882
3168
 
2883
3169
  // platforms/nextjs.ts
3170
+ var appTelemetry = {
3171
+ sdk: SDK_TELEMETRY,
3172
+ framework: "nextjs",
3173
+ runtime: `node@${process.version}`
3174
+ };
3175
+ var pagesTelemetry = {
3176
+ sdk: SDK_TELEMETRY,
3177
+ framework: "nextjs-pages",
3178
+ runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
3179
+ };
2884
3180
  var serve = (routeFunction, options) => {
2885
3181
  const { handler: serveHandler } = serveBase(
2886
3182
  routeFunction,
2887
- {
2888
- sdk: SDK_TELEMETRY,
2889
- framework: "nextjs",
2890
- runtime: `node@${process.version}`
2891
- },
3183
+ appTelemetry,
2892
3184
  options
2893
3185
  );
2894
3186
  return {
@@ -2897,16 +3189,28 @@ var serve = (routeFunction, options) => {
2897
3189
  }
2898
3190
  };
2899
3191
  };
2900
- var servePagesRouter = (routeFunction, options) => {
2901
- const { handler: serveHandler } = serveBase(
3192
+ var createWorkflow = (...params) => {
3193
+ const [routeFunction, options = {}] = params;
3194
+ return {
2902
3195
  routeFunction,
2903
- {
2904
- sdk: SDK_TELEMETRY,
2905
- framework: "nextjs-pages",
2906
- runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
2907
- },
2908
- options
2909
- );
3196
+ options,
3197
+ workflowId: void 0
3198
+ };
3199
+ };
3200
+ var serveMany = (workflows, options) => {
3201
+ return {
3202
+ POST: serveManyBase({
3203
+ workflows,
3204
+ getUrl(params) {
3205
+ return params.url;
3206
+ },
3207
+ serveMethod: (...params) => serve(...params).POST,
3208
+ options
3209
+ }).handler
3210
+ };
3211
+ };
3212
+ var servePagesRouter = (routeFunction, options) => {
3213
+ const { handler: serveHandler } = serveBase(routeFunction, pagesTelemetry, options);
2910
3214
  const handler = async (request_, res) => {
2911
3215
  if (request_.method?.toUpperCase() !== "POST") {
2912
3216
  res.status(405).json("Only POST requests are allowed in worklfows");
@@ -2925,10 +3229,37 @@ var servePagesRouter = (routeFunction, options) => {
2925
3229
  const response = await serveHandler(request);
2926
3230
  res.status(response.status).json(await response.json());
2927
3231
  };
2928
- return { handler };
3232
+ return {
3233
+ handler
3234
+ };
3235
+ };
3236
+ var createWorkflowPagesRouter = (...params) => {
3237
+ const [routeFunction, options = {}] = params;
3238
+ return {
3239
+ routeFunction,
3240
+ options,
3241
+ workflowId: void 0
3242
+ };
3243
+ };
3244
+ var serveManyPagesRouter = (workflows, options) => {
3245
+ return serveManyBase({
3246
+ workflows,
3247
+ getUrl(request_) {
3248
+ const protocol = request_.headers["x-forwarded-proto"];
3249
+ const host = request_.headers.host;
3250
+ const baseUrl = `${protocol}://${host}`;
3251
+ return `${baseUrl}${request_.url}`;
3252
+ },
3253
+ serveMethod: (...params) => servePagesRouter(...params).handler,
3254
+ options
3255
+ });
2929
3256
  };
2930
3257
  // Annotate the CommonJS export names for ESM import in node:
2931
3258
  0 && (module.exports = {
3259
+ createWorkflow,
3260
+ createWorkflowPagesRouter,
2932
3261
  serve,
3262
+ serveMany,
3263
+ serveManyPagesRouter,
2933
3264
  servePagesRouter
2934
3265
  });