@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/h3.js CHANGED
@@ -20,7 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // platforms/h3.ts
21
21
  var h3_exports = {};
22
22
  __export(h3_exports, {
23
- serve: () => serve
23
+ createWorkflow: () => createWorkflow,
24
+ serve: () => serve,
25
+ serveMany: () => serveMany
24
26
  });
25
27
  module.exports = __toCommonJS(h3_exports);
26
28
 
@@ -395,12 +397,13 @@ var WORKFLOW_INIT_HEADER = "Upstash-Workflow-Init";
395
397
  var WORKFLOW_URL_HEADER = "Upstash-Workflow-Url";
396
398
  var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
397
399
  var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
400
+ var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
398
401
  var WORKFLOW_PROTOCOL_VERSION = "1";
399
402
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
400
403
  var DEFAULT_CONTENT_TYPE = "application/json";
401
404
  var NO_CONCURRENCY = 1;
402
405
  var DEFAULT_RETRIES = 3;
403
- var VERSION = "v0.2.3";
406
+ var VERSION = "v0.2.7";
404
407
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
405
408
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
406
409
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -447,6 +450,31 @@ var formatWorkflowError = (error) => {
447
450
  };
448
451
  };
449
452
 
453
+ // src/utils.ts
454
+ var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
455
+ var NANOID_LENGTH = 21;
456
+ function getRandomInt() {
457
+ return Math.floor(Math.random() * NANOID_CHARS.length);
458
+ }
459
+ function nanoid() {
460
+ return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
461
+ }
462
+ function getWorkflowRunId(id) {
463
+ return `wfr_${id ?? nanoid()}`;
464
+ }
465
+ function decodeBase64(base64) {
466
+ try {
467
+ const binString = atob(base64);
468
+ const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
469
+ return new TextDecoder().decode(intArray);
470
+ } catch (error) {
471
+ console.warn(
472
+ `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
473
+ );
474
+ return atob(base64);
475
+ }
476
+ }
477
+
450
478
  // src/context/steps.ts
451
479
  var BaseLazyStep = class {
452
480
  stepName;
@@ -551,8 +579,9 @@ var LazyCallStep = class extends BaseLazyStep {
551
579
  headers;
552
580
  retries;
553
581
  timeout;
582
+ flowControl;
554
583
  stepType = "Call";
555
- constructor(stepName, url, method, body, headers, retries, timeout) {
584
+ constructor(stepName, url, method, body, headers, retries, timeout, flowControl) {
556
585
  super(stepName);
557
586
  this.url = url;
558
587
  this.method = method;
@@ -560,6 +589,7 @@ var LazyCallStep = class extends BaseLazyStep {
560
589
  this.headers = headers;
561
590
  this.retries = retries;
562
591
  this.timeout = timeout;
592
+ this.flowControl = flowControl;
563
593
  }
564
594
  getPlanStep(concurrent, targetStep) {
565
595
  return {
@@ -627,6 +657,49 @@ var LazyNotifyStep = class extends LazyFunctionStep {
627
657
  });
628
658
  }
629
659
  };
660
+ var LazyInvokeStep = class extends BaseLazyStep {
661
+ stepType = "Invoke";
662
+ params;
663
+ constructor(stepName, {
664
+ workflow,
665
+ body,
666
+ headers = {},
667
+ workflowRunId,
668
+ retries,
669
+ flowControl
670
+ }) {
671
+ super(stepName);
672
+ this.params = {
673
+ workflow,
674
+ body,
675
+ headers,
676
+ workflowRunId: getWorkflowRunId(workflowRunId),
677
+ retries,
678
+ flowControl
679
+ };
680
+ }
681
+ getPlanStep(concurrent, targetStep) {
682
+ return {
683
+ stepId: 0,
684
+ stepName: this.stepName,
685
+ stepType: this.stepType,
686
+ concurrent,
687
+ targetStep
688
+ };
689
+ }
690
+ /**
691
+ * won't be used as it's the server who will add the result step
692
+ * in Invoke step.
693
+ */
694
+ getResultStep(concurrent, stepId) {
695
+ return Promise.resolve({
696
+ stepId,
697
+ stepName: this.stepName,
698
+ stepType: this.stepType,
699
+ concurrent
700
+ });
701
+ }
702
+ };
630
703
 
631
704
  // node_modules/neverthrow/dist/index.es.js
632
705
  var defaultErrorConfig = {
@@ -1051,7 +1124,8 @@ var StepTypes = [
1051
1124
  "SleepUntil",
1052
1125
  "Call",
1053
1126
  "Wait",
1054
- "Notify"
1127
+ "Notify",
1128
+ "Invoke"
1055
1129
  ];
1056
1130
 
1057
1131
  // src/workflow-requests.ts
@@ -1059,8 +1133,9 @@ var import_qstash3 = require("@upstash/qstash");
1059
1133
  var triggerFirstInvocation = async ({
1060
1134
  workflowContext,
1061
1135
  useJSONContent,
1062
- telemetry,
1063
- debug
1136
+ telemetry: telemetry2,
1137
+ debug,
1138
+ invokeCount
1064
1139
  }) => {
1065
1140
  const { headers } = getHeaders({
1066
1141
  initHeaderValue: "true",
@@ -1069,7 +1144,9 @@ var triggerFirstInvocation = async ({
1069
1144
  userHeaders: workflowContext.headers,
1070
1145
  failureUrl: workflowContext.failureUrl,
1071
1146
  retries: workflowContext.retries,
1072
- telemetry
1147
+ telemetry: telemetry2,
1148
+ invokeCount,
1149
+ flowControl: workflowContext.flowControl
1073
1150
  });
1074
1151
  if (workflowContext.headers.get("content-type")) {
1075
1152
  headers["content-type"] = workflowContext.headers.get("content-type");
@@ -1115,8 +1192,8 @@ var triggerRouteFunction = async ({
1115
1192
  debug
1116
1193
  }) => {
1117
1194
  try {
1118
- await onStep();
1119
- await onCleanup();
1195
+ const result = await onStep();
1196
+ await onCleanup(result);
1120
1197
  return ok("workflow-finished");
1121
1198
  } catch (error) {
1122
1199
  const error_ = error;
@@ -1137,14 +1214,15 @@ var triggerRouteFunction = async ({
1137
1214
  }
1138
1215
  }
1139
1216
  };
1140
- var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
1217
+ var triggerWorkflowDelete = async (workflowContext, result, debug, cancel = false) => {
1141
1218
  await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
1142
1219
  deletedWorkflowRunId: workflowContext.workflowRunId
1143
1220
  });
1144
1221
  await workflowContext.qstashClient.http.request({
1145
1222
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
1146
1223
  method: "DELETE",
1147
- parseResponseAsJson: false
1224
+ parseResponseAsJson: false,
1225
+ body: JSON.stringify(result)
1148
1226
  });
1149
1227
  await debug?.log(
1150
1228
  "SUBMIT",
@@ -1173,7 +1251,8 @@ var handleThirdPartyCallResult = async ({
1173
1251
  workflowUrl,
1174
1252
  failureUrl,
1175
1253
  retries,
1176
- telemetry,
1254
+ telemetry: telemetry2,
1255
+ flowControl,
1177
1256
  debug
1178
1257
  }) => {
1179
1258
  try {
@@ -1221,6 +1300,7 @@ ${atob(callbackMessage.body ?? "")}`
1221
1300
  const stepType = request.headers.get("Upstash-Workflow-StepType");
1222
1301
  const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
1223
1302
  const contentType = request.headers.get("Upstash-Workflow-ContentType");
1303
+ const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
1224
1304
  if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
1225
1305
  throw new Error(
1226
1306
  `Missing info in callback message source header: ${JSON.stringify({
@@ -1241,7 +1321,9 @@ ${atob(callbackMessage.body ?? "")}`
1241
1321
  userHeaders,
1242
1322
  failureUrl,
1243
1323
  retries,
1244
- telemetry
1324
+ telemetry: telemetry2,
1325
+ invokeCount: Number(invokeCount),
1326
+ flowControl
1245
1327
  });
1246
1328
  const callResponse = {
1247
1329
  status: callbackMessage.status,
@@ -1280,11 +1362,11 @@ ${atob(callbackMessage.body ?? "")}`
1280
1362
  );
1281
1363
  }
1282
1364
  };
1283
- var getTelemetryHeaders = (telemetry) => {
1365
+ var getTelemetryHeaders = (telemetry2) => {
1284
1366
  return {
1285
- [TELEMETRY_HEADER_SDK]: telemetry.sdk,
1286
- [TELEMETRY_HEADER_FRAMEWORK]: telemetry.framework,
1287
- [TELEMETRY_HEADER_RUNTIME]: telemetry.runtime ?? "unknown"
1367
+ [TELEMETRY_HEADER_SDK]: telemetry2.sdk,
1368
+ [TELEMETRY_HEADER_FRAMEWORK]: telemetry2.framework,
1369
+ [TELEMETRY_HEADER_RUNTIME]: telemetry2.runtime ?? "unknown"
1288
1370
  };
1289
1371
  };
1290
1372
  var getHeaders = ({
@@ -1297,15 +1379,24 @@ var getHeaders = ({
1297
1379
  step,
1298
1380
  callRetries,
1299
1381
  callTimeout,
1300
- telemetry
1382
+ telemetry: telemetry2,
1383
+ invokeCount,
1384
+ flowControl,
1385
+ callFlowControl
1301
1386
  }) => {
1387
+ const contentType = (userHeaders ? userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
1302
1388
  const baseHeaders = {
1303
1389
  [WORKFLOW_INIT_HEADER]: initHeaderValue,
1304
1390
  [WORKFLOW_ID_HEADER]: workflowRunId,
1305
1391
  [WORKFLOW_URL_HEADER]: workflowUrl,
1306
1392
  [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody",
1307
- ...telemetry ? getTelemetryHeaders(telemetry) : {}
1393
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1394
+ "content-type": contentType,
1395
+ ...telemetry2 ? getTelemetryHeaders(telemetry2) : {}
1308
1396
  };
1397
+ if (invokeCount !== void 0 && !step?.callUrl) {
1398
+ baseHeaders[`Upstash-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`] = invokeCount.toString();
1399
+ }
1309
1400
  if (!step?.callUrl) {
1310
1401
  baseHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
1311
1402
  }
@@ -1322,6 +1413,11 @@ var getHeaders = ({
1322
1413
  if (retries !== void 0) {
1323
1414
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1324
1415
  }
1416
+ if (flowControl) {
1417
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1418
+ baseHeaders["Upstash-Failure-Callback-Flow-Control-Key"] = flowControlKey;
1419
+ baseHeaders["Upstash-Failure-Callback-Flow-Control-Value"] = flowControlValue;
1420
+ }
1325
1421
  if (!step?.callUrl) {
1326
1422
  baseHeaders["Upstash-Failure-Callback"] = failureUrl;
1327
1423
  }
@@ -1333,9 +1429,26 @@ var getHeaders = ({
1333
1429
  baseHeaders["Upstash-Callback-Retries"] = retries.toString();
1334
1430
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1335
1431
  }
1336
- } else if (retries !== void 0) {
1337
- baseHeaders["Upstash-Retries"] = retries.toString();
1338
- baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1432
+ if (callFlowControl) {
1433
+ const { flowControlKey, flowControlValue } = prepareFlowControl(callFlowControl);
1434
+ baseHeaders["Upstash-Flow-Control-Key"] = flowControlKey;
1435
+ baseHeaders["Upstash-Flow-Control-Value"] = flowControlValue;
1436
+ }
1437
+ if (flowControl) {
1438
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1439
+ baseHeaders["Upstash-Callback-Flow-Control-Key"] = flowControlKey;
1440
+ baseHeaders["Upstash-Callback-Flow-Control-Value"] = flowControlValue;
1441
+ }
1442
+ } else {
1443
+ if (flowControl) {
1444
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1445
+ baseHeaders["Upstash-Flow-Control-Key"] = flowControlKey;
1446
+ baseHeaders["Upstash-Flow-Control-Value"] = flowControlValue;
1447
+ }
1448
+ if (retries !== void 0) {
1449
+ baseHeaders["Upstash-Retries"] = retries.toString();
1450
+ baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1451
+ }
1339
1452
  }
1340
1453
  if (userHeaders) {
1341
1454
  for (const header of userHeaders.keys()) {
@@ -1347,7 +1460,6 @@ var getHeaders = ({
1347
1460
  baseHeaders[`Upstash-Failure-Callback-Forward-${header}`] = userHeaders.get(header);
1348
1461
  }
1349
1462
  }
1350
- const contentType = (userHeaders ? userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
1351
1463
  if (step?.callHeaders) {
1352
1464
  const forwardedHeaders = Object.fromEntries(
1353
1465
  Object.entries(step.callHeaders).map(([header, value]) => [
@@ -1371,6 +1483,7 @@ var getHeaders = ({
1371
1483
  "Upstash-Callback-Forward-Upstash-Workflow-StepType": step.stepType,
1372
1484
  "Upstash-Callback-Forward-Upstash-Workflow-Concurrent": step.concurrent.toString(),
1373
1485
  "Upstash-Callback-Forward-Upstash-Workflow-ContentType": contentType,
1486
+ [`Upstash-Callback-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`]: (invokeCount ?? 0).toString(),
1374
1487
  "Upstash-Workflow-CallType": "toCallback"
1375
1488
  }
1376
1489
  };
@@ -1387,8 +1500,8 @@ var getHeaders = ({
1387
1500
  Object.entries(baseHeaders).map(([header, value]) => [header, [value]])
1388
1501
  ),
1389
1502
  // to include telemetry headers:
1390
- ...telemetry ? Object.fromEntries(
1391
- Object.entries(getTelemetryHeaders(telemetry)).map(([header, value]) => [
1503
+ ...telemetry2 ? Object.fromEntries(
1504
+ Object.entries(getTelemetryHeaders(telemetry2)).map(([header, value]) => [
1392
1505
  header,
1393
1506
  [value]
1394
1507
  ])
@@ -1397,8 +1510,7 @@ var getHeaders = ({
1397
1510
  "Upstash-Workflow-Runid": [workflowRunId],
1398
1511
  [WORKFLOW_INIT_HEADER]: ["false"],
1399
1512
  [WORKFLOW_URL_HEADER]: [workflowUrl],
1400
- "Upstash-Workflow-CallType": ["step"],
1401
- "Content-Type": [contentType]
1513
+ "Upstash-Workflow-CallType": ["step"]
1402
1514
  }
1403
1515
  };
1404
1516
  }
@@ -1429,9 +1541,159 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
1429
1541
  );
1430
1542
  }
1431
1543
  };
1544
+ var prepareFlowControl = (flowControl) => {
1545
+ const parallelism = flowControl.parallelism?.toString();
1546
+ const rate = flowControl.ratePerSecond?.toString();
1547
+ const controlValue = [
1548
+ parallelism ? `parallelism=${parallelism}` : void 0,
1549
+ rate ? `rate=${rate}` : void 0
1550
+ ].filter(Boolean);
1551
+ if (controlValue.length === 0) {
1552
+ throw new import_qstash3.QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
1553
+ }
1554
+ return {
1555
+ flowControlKey: flowControl.key,
1556
+ flowControlValue: controlValue.join(", ")
1557
+ };
1558
+ };
1432
1559
 
1433
1560
  // src/context/auto-executor.ts
1434
1561
  var import_qstash4 = require("@upstash/qstash");
1562
+
1563
+ // src/serve/serve-many.ts
1564
+ var getWorkflowId = (url) => {
1565
+ const components = url.split("/");
1566
+ const lastComponent = components[components.length - 1];
1567
+ return lastComponent.split("?")[0];
1568
+ };
1569
+ var serveManyBase = ({
1570
+ workflows,
1571
+ getUrl: getUrl2,
1572
+ serveMethod,
1573
+ options
1574
+ }) => {
1575
+ const workflowIds = [];
1576
+ const workflowMap = Object.fromEntries(
1577
+ Object.entries(workflows).map((workflow) => {
1578
+ const workflowId = workflow[0];
1579
+ if (workflowIds.includes(workflowId)) {
1580
+ throw new WorkflowError(
1581
+ `Duplicate workflow name found: '${workflowId}'. Please set different workflow names in serveMany.`
1582
+ );
1583
+ }
1584
+ if (workflowId.includes("/")) {
1585
+ throw new WorkflowError(
1586
+ `Invalid workflow name found: '${workflowId}'. Workflow name cannot contain '/'.`
1587
+ );
1588
+ }
1589
+ workflowIds.push(workflowId);
1590
+ workflow[1].workflowId = workflowId;
1591
+ workflow[1].options = {
1592
+ ...options,
1593
+ ...workflow[1].options
1594
+ };
1595
+ const params = [workflow[1].routeFunction, workflow[1].options];
1596
+ const handler = serveMethod(...params);
1597
+ return [workflowId, handler];
1598
+ })
1599
+ );
1600
+ return {
1601
+ handler: async (...params) => {
1602
+ const url = getUrl2(...params);
1603
+ const pickedWorkflowId = getWorkflowId(url);
1604
+ if (!pickedWorkflowId) {
1605
+ return new Response(
1606
+ `Unexpected request in serveMany. workflowId not set. Please update the URL of your request.`,
1607
+ {
1608
+ status: 404
1609
+ }
1610
+ );
1611
+ }
1612
+ const workflow = workflowMap[pickedWorkflowId];
1613
+ if (!workflow) {
1614
+ return new Response(
1615
+ `No workflows in serveMany found for '${pickedWorkflowId}'. Please update the URL of your request.`,
1616
+ {
1617
+ status: 404
1618
+ }
1619
+ );
1620
+ }
1621
+ return await workflow(...params);
1622
+ }
1623
+ };
1624
+ };
1625
+ var invokeWorkflow = async ({
1626
+ settings,
1627
+ invokeStep,
1628
+ context,
1629
+ invokeCount,
1630
+ telemetry: telemetry2
1631
+ }) => {
1632
+ const {
1633
+ body,
1634
+ workflow,
1635
+ headers = {},
1636
+ workflowRunId = getWorkflowRunId(),
1637
+ retries,
1638
+ flowControl
1639
+ } = settings;
1640
+ const { workflowId } = workflow;
1641
+ const {
1642
+ retries: workflowRetries,
1643
+ failureFunction,
1644
+ failureUrl,
1645
+ useJSONContent,
1646
+ flowControl: workflowFlowControl
1647
+ } = workflow.options;
1648
+ if (!workflowId) {
1649
+ throw new WorkflowError("You can only invoke workflow which has a workflowId");
1650
+ }
1651
+ const { headers: invokerHeaders } = getHeaders({
1652
+ initHeaderValue: "false",
1653
+ workflowRunId: context.workflowRunId,
1654
+ workflowUrl: context.url,
1655
+ userHeaders: context.headers,
1656
+ failureUrl: context.failureUrl,
1657
+ retries: context.retries,
1658
+ telemetry: telemetry2,
1659
+ invokeCount,
1660
+ flowControl: context.flowControl
1661
+ });
1662
+ invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
1663
+ const newUrl = context.url.replace(/[^/]+$/, workflowId);
1664
+ const { headers: triggerHeaders } = getHeaders({
1665
+ initHeaderValue: "true",
1666
+ workflowRunId,
1667
+ workflowUrl: newUrl,
1668
+ userHeaders: new Headers(headers),
1669
+ retries: retries ?? workflowRetries,
1670
+ telemetry: telemetry2,
1671
+ failureUrl: failureFunction ? newUrl : failureUrl,
1672
+ invokeCount: invokeCount + 1,
1673
+ flowControl: flowControl ?? workflowFlowControl
1674
+ });
1675
+ triggerHeaders["Upstash-Workflow-Invoke"] = "true";
1676
+ if (useJSONContent) {
1677
+ triggerHeaders["content-type"] = "application/json";
1678
+ }
1679
+ const request = {
1680
+ body: JSON.stringify(body),
1681
+ headers: Object.fromEntries(
1682
+ Object.entries(invokerHeaders).map((pairs) => [pairs[0], [pairs[1]]])
1683
+ ),
1684
+ workflowRunId,
1685
+ workflowUrl: context.url,
1686
+ step: invokeStep
1687
+ };
1688
+ await context.qstashClient.publish({
1689
+ headers: triggerHeaders,
1690
+ method: "POST",
1691
+ body: JSON.stringify(request),
1692
+ url: newUrl
1693
+ });
1694
+ };
1695
+
1696
+ // src/context/auto-executor.ts
1435
1697
  var AutoExecutor = class _AutoExecutor {
1436
1698
  context;
1437
1699
  promises = /* @__PURE__ */ new WeakMap();
@@ -1440,14 +1702,16 @@ var AutoExecutor = class _AutoExecutor {
1440
1702
  nonPlanStepCount;
1441
1703
  steps;
1442
1704
  indexInCurrentList = 0;
1705
+ invokeCount;
1443
1706
  telemetry;
1444
1707
  stepCount = 0;
1445
1708
  planStepCount = 0;
1446
1709
  executingStep = false;
1447
- constructor(context, steps, telemetry, debug) {
1710
+ constructor(context, steps, telemetry2, invokeCount, debug) {
1448
1711
  this.context = context;
1449
1712
  this.steps = steps;
1450
- this.telemetry = telemetry;
1713
+ this.telemetry = telemetry2;
1714
+ this.invokeCount = invokeCount ?? 0;
1451
1715
  this.debug = debug;
1452
1716
  this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
1453
1717
  }
@@ -1670,7 +1934,9 @@ var AutoExecutor = class _AutoExecutor {
1670
1934
  step: waitStep,
1671
1935
  failureUrl: this.context.failureUrl,
1672
1936
  retries: this.context.retries,
1673
- telemetry: this.telemetry
1937
+ telemetry: this.telemetry,
1938
+ invokeCount: this.invokeCount,
1939
+ flowControl: this.context.flowControl
1674
1940
  });
1675
1941
  const waitBody = {
1676
1942
  url: this.context.url,
@@ -1693,7 +1959,19 @@ var AutoExecutor = class _AutoExecutor {
1693
1959
  method: "POST",
1694
1960
  parseResponseAsJson: false
1695
1961
  });
1696
- throw new WorkflowAbort(steps[0].stepName, steps[0]);
1962
+ throw new WorkflowAbort(waitStep.stepName, waitStep);
1963
+ }
1964
+ if (steps.length === 1 && lazySteps[0] instanceof LazyInvokeStep) {
1965
+ const invokeStep = steps[0];
1966
+ const lazyInvokeStep = lazySteps[0];
1967
+ await invokeWorkflow({
1968
+ settings: lazyInvokeStep.params,
1969
+ invokeStep,
1970
+ context: this.context,
1971
+ invokeCount: this.invokeCount,
1972
+ telemetry: this.telemetry
1973
+ });
1974
+ throw new WorkflowAbort(invokeStep.stepName, invokeStep);
1697
1975
  }
1698
1976
  const result = await this.context.qstashClient.batchJSON(
1699
1977
  steps.map((singleStep, index) => {
@@ -1708,11 +1986,14 @@ var AutoExecutor = class _AutoExecutor {
1708
1986
  retries: this.context.retries,
1709
1987
  callRetries: lazyStep instanceof LazyCallStep ? lazyStep.retries : void 0,
1710
1988
  callTimeout: lazyStep instanceof LazyCallStep ? lazyStep.timeout : void 0,
1711
- telemetry: this.telemetry
1989
+ telemetry: this.telemetry,
1990
+ invokeCount: this.invokeCount,
1991
+ flowControl: this.context.flowControl,
1992
+ callFlowControl: lazyStep instanceof LazyCallStep ? lazyStep.flowControl : void 0
1712
1993
  });
1713
1994
  const willWait = singleStep.concurrent === NO_CONCURRENCY || singleStep.stepId === 0;
1714
1995
  singleStep.out = JSON.stringify(singleStep.out);
1715
- return singleStep.callUrl ? (
1996
+ return singleStep.callUrl && lazyStep instanceof LazyCallStep ? (
1716
1997
  // if the step is a third party call, we call the third party
1717
1998
  // url (singleStep.callUrl) and pass information about the workflow
1718
1999
  // in the headers (handled in getHeaders). QStash makes the request
@@ -2369,6 +2650,11 @@ var WorkflowContext = class {
2369
2650
  * Number of retries
2370
2651
  */
2371
2652
  retries;
2653
+ /**
2654
+ * Settings for controlling the number of active requests
2655
+ * and number of requests per second with the same key.
2656
+ */
2657
+ flowControl;
2372
2658
  constructor({
2373
2659
  qstashClient,
2374
2660
  workflowRunId,
@@ -2380,7 +2666,9 @@ var WorkflowContext = class {
2380
2666
  initialPayload,
2381
2667
  env,
2382
2668
  retries,
2383
- telemetry
2669
+ telemetry: telemetry2,
2670
+ invokeCount,
2671
+ flowControl
2384
2672
  }) {
2385
2673
  this.qstashClient = qstashClient;
2386
2674
  this.workflowRunId = workflowRunId;
@@ -2391,7 +2679,8 @@ var WorkflowContext = class {
2391
2679
  this.requestPayload = initialPayload;
2392
2680
  this.env = env ?? {};
2393
2681
  this.retries = retries ?? DEFAULT_RETRIES;
2394
- this.executor = new AutoExecutor(this, this.steps, telemetry, debug);
2682
+ this.flowControl = flowControl;
2683
+ this.executor = new AutoExecutor(this, this.steps, telemetry2, invokeCount, debug);
2395
2684
  }
2396
2685
  /**
2397
2686
  * Executes a workflow step
@@ -2493,7 +2782,7 @@ var WorkflowContext = class {
2493
2782
  * }
2494
2783
  */
2495
2784
  async call(stepName, settings) {
2496
- const { url, method = "GET", body, headers = {}, retries = 0, timeout } = settings;
2785
+ const { url, method = "GET", body, headers = {}, retries = 0, timeout, flowControl } = settings;
2497
2786
  const result = await this.addStep(
2498
2787
  new LazyCallStep(
2499
2788
  stepName,
@@ -2502,7 +2791,8 @@ var WorkflowContext = class {
2502
2791
  body,
2503
2792
  headers,
2504
2793
  retries,
2505
- timeout
2794
+ timeout,
2795
+ flowControl
2506
2796
  )
2507
2797
  );
2508
2798
  if (typeof result === "string") {
@@ -2611,6 +2901,13 @@ var WorkflowContext = class {
2611
2901
  return result;
2612
2902
  }
2613
2903
  }
2904
+ async invoke(stepName, settings) {
2905
+ const result = await this.addStep(new LazyInvokeStep(stepName, settings));
2906
+ return {
2907
+ ...result,
2908
+ body: result.body ? JSON.parse(result.body) : void 0
2909
+ };
2910
+ }
2614
2911
  /**
2615
2912
  * Cancel the current workflow run
2616
2913
  *
@@ -2688,31 +2985,6 @@ var WorkflowLogger = class _WorkflowLogger {
2688
2985
  }
2689
2986
  };
2690
2987
 
2691
- // src/utils.ts
2692
- var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
2693
- var NANOID_LENGTH = 21;
2694
- function getRandomInt() {
2695
- return Math.floor(Math.random() * NANOID_CHARS.length);
2696
- }
2697
- function nanoid() {
2698
- return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
2699
- }
2700
- function getWorkflowRunId(id) {
2701
- return `wfr_${id ?? nanoid()}`;
2702
- }
2703
- function decodeBase64(base64) {
2704
- try {
2705
- const binString = atob(base64);
2706
- const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
2707
- return new TextDecoder().decode(intArray);
2708
- } catch (error) {
2709
- console.warn(
2710
- `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
2711
- );
2712
- return atob(base64);
2713
- }
2714
- }
2715
-
2716
2988
  // src/serve/authorization.ts
2717
2989
  var import_qstash8 = require("@upstash/qstash");
2718
2990
  var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
@@ -2757,7 +3029,8 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
2757
3029
  failureUrl: context.failureUrl,
2758
3030
  initialPayload: context.requestPayload,
2759
3031
  env: context.env,
2760
- retries: context.retries
3032
+ retries: context.retries,
3033
+ flowControl: context.flowControl
2761
3034
  });
2762
3035
  try {
2763
3036
  await routeFunction(disabledContext);
@@ -2910,7 +3183,7 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
2910
3183
  };
2911
3184
  }
2912
3185
  };
2913
- var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, debug) => {
3186
+ var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, flowControl, debug) => {
2914
3187
  if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
2915
3188
  return ok("not-failure-callback");
2916
3189
  }
@@ -2922,22 +3195,21 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
2922
3195
  );
2923
3196
  }
2924
3197
  try {
2925
- const { status, header, body, url, sourceHeader, sourceBody, workflowRunId } = JSON.parse(
2926
- requestPayload
2927
- );
3198
+ const { status, header, body, url, sourceBody, workflowRunId } = JSON.parse(requestPayload);
2928
3199
  const decodedBody = body ? decodeBase64(body) : "{}";
2929
3200
  const errorPayload = JSON.parse(decodedBody);
2930
3201
  const workflowContext = new WorkflowContext({
2931
3202
  qstashClient,
2932
3203
  workflowRunId,
2933
3204
  initialPayload: sourceBody ? initialPayloadParser(decodeBase64(sourceBody)) : void 0,
2934
- headers: recreateUserHeaders(new Headers(sourceHeader)),
3205
+ headers: recreateUserHeaders(request.headers),
2935
3206
  steps: [],
2936
3207
  url,
2937
3208
  failureUrl: url,
2938
3209
  debug,
2939
3210
  env,
2940
3211
  retries,
3212
+ flowControl,
2941
3213
  telemetry: void 0
2942
3214
  // not going to make requests in authentication check
2943
3215
  });
@@ -3050,7 +3322,7 @@ var determineUrls = async (request, url, baseUrl, failureFunction, failureUrl, d
3050
3322
  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`;
3051
3323
 
3052
3324
  // src/serve/index.ts
3053
- var serveBase = (routeFunction, telemetry, options) => {
3325
+ var serveBase = (routeFunction, telemetry2, options) => {
3054
3326
  const {
3055
3327
  qstashClient,
3056
3328
  onStepFinish,
@@ -3064,9 +3336,10 @@ var serveBase = (routeFunction, telemetry, options) => {
3064
3336
  env,
3065
3337
  retries,
3066
3338
  useJSONContent,
3067
- disableTelemetry
3339
+ disableTelemetry,
3340
+ flowControl
3068
3341
  } = processOptions(options);
3069
- telemetry = disableTelemetry ? void 0 : telemetry;
3342
+ telemetry2 = disableTelemetry ? void 0 : telemetry2;
3070
3343
  const debug = WorkflowLogger.getLogger(verbose);
3071
3344
  const handler = async (request) => {
3072
3345
  await debug?.log("INFO", "ENDPOINT_START");
@@ -3105,6 +3378,7 @@ var serveBase = (routeFunction, telemetry, options) => {
3105
3378
  failureFunction,
3106
3379
  env,
3107
3380
  retries,
3381
+ flowControl,
3108
3382
  debug
3109
3383
  );
3110
3384
  if (failureCheck.isErr()) {
@@ -3113,6 +3387,7 @@ var serveBase = (routeFunction, telemetry, options) => {
3113
3387
  await debug?.log("WARN", "RESPONSE_DEFAULT", "failureFunction executed");
3114
3388
  return onStepFinish(workflowRunId, "failure-callback");
3115
3389
  }
3390
+ const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
3116
3391
  const workflowContext = new WorkflowContext({
3117
3392
  qstashClient,
3118
3393
  workflowRunId,
@@ -3124,7 +3399,9 @@ var serveBase = (routeFunction, telemetry, options) => {
3124
3399
  debug,
3125
3400
  env,
3126
3401
  retries,
3127
- telemetry
3402
+ telemetry: telemetry2,
3403
+ invokeCount,
3404
+ flowControl
3128
3405
  });
3129
3406
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
3130
3407
  routeFunction,
@@ -3147,7 +3424,8 @@ var serveBase = (routeFunction, telemetry, options) => {
3147
3424
  workflowUrl,
3148
3425
  failureUrl: workflowFailureUrl,
3149
3426
  retries,
3150
- telemetry,
3427
+ flowControl,
3428
+ telemetry: telemetry2,
3151
3429
  debug
3152
3430
  });
3153
3431
  if (callReturnCheck.isErr()) {
@@ -3156,10 +3434,16 @@ var serveBase = (routeFunction, telemetry, options) => {
3156
3434
  });
3157
3435
  throw callReturnCheck.error;
3158
3436
  } else if (callReturnCheck.value === "continue-workflow") {
3159
- const result = isFirstInvocation ? await triggerFirstInvocation({ workflowContext, useJSONContent, telemetry, debug }) : await triggerRouteFunction({
3437
+ const result = isFirstInvocation ? await triggerFirstInvocation({
3438
+ workflowContext,
3439
+ useJSONContent,
3440
+ telemetry: telemetry2,
3441
+ debug,
3442
+ invokeCount
3443
+ }) : await triggerRouteFunction({
3160
3444
  onStep: async () => routeFunction(workflowContext),
3161
- onCleanup: async () => {
3162
- await triggerWorkflowDelete(workflowContext, debug);
3445
+ onCleanup: async (result2) => {
3446
+ await triggerWorkflowDelete(workflowContext, result2, debug);
3163
3447
  },
3164
3448
  onCancel: async () => {
3165
3449
  await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
@@ -3199,39 +3483,57 @@ function transformHeaders(headers) {
3199
3483
  ]);
3200
3484
  return formattedHeaders;
3201
3485
  }
3486
+ function getUrl(event) {
3487
+ const request_ = event.node.req;
3488
+ const protocol = request_.headers["x-forwarded-proto"];
3489
+ const host = request_.headers.host;
3490
+ const url = `${protocol}://${host}${event.path}`;
3491
+ return url;
3492
+ }
3493
+ var telemetry = {
3494
+ sdk: SDK_TELEMETRY,
3495
+ framework: "h3",
3496
+ runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
3497
+ };
3202
3498
  var serve = (routeFunction, options) => {
3203
3499
  const handler = defineEventHandler(async (event) => {
3204
3500
  const method = event.node.req.method;
3205
3501
  if (method?.toUpperCase() !== "POST") {
3206
- return {
3207
- status: 405,
3208
- body: "Only POST requests are allowed in worklfows"
3209
- };
3502
+ return new Response("Only POST requests are allowed in worklfows", {
3503
+ status: 405
3504
+ });
3210
3505
  }
3211
- const request_ = event.node.req;
3212
- const protocol = request_.headers["x-forwarded-proto"];
3213
- const host = request_.headers.host;
3214
- const url = `${protocol}://${host}${event.path}`;
3215
- const headers = transformHeaders(request_.headers);
3506
+ const url = getUrl(event);
3507
+ const headers = transformHeaders(event.node.req.headers);
3216
3508
  const request = new Request(url, {
3217
3509
  headers,
3218
3510
  body: await readRawBody(event),
3219
3511
  method: "POST"
3220
3512
  });
3221
- const { handler: serveHandler } = serveBase(
3222
- routeFunction,
3223
- {
3224
- sdk: SDK_TELEMETRY,
3225
- framework: "h3",
3226
- runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
3227
- },
3228
- options
3229
- );
3513
+ const { handler: serveHandler } = serveBase(routeFunction, telemetry, options);
3230
3514
  return await serveHandler(request);
3231
3515
  });
3232
3516
  return { handler };
3233
3517
  };
3518
+ var createWorkflow = (...params) => {
3519
+ const [routeFunction, options = {}] = params;
3520
+ return {
3521
+ routeFunction,
3522
+ options,
3523
+ workflowId: void 0
3524
+ };
3525
+ };
3526
+ var serveMany = (workflows, options) => {
3527
+ return serveManyBase({
3528
+ workflows,
3529
+ getUrl,
3530
+ serveMethod: (...params) => serve(...params).handler,
3531
+ options
3532
+ });
3533
+ };
3234
3534
  // Annotate the CommonJS export names for ESM import in node:
3235
3535
  0 && (module.exports = {
3236
- serve
3536
+ createWorkflow,
3537
+ serve,
3538
+ serveMany
3237
3539
  });