@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/svelte.js CHANGED
@@ -20,7 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // platforms/svelte.ts
21
21
  var svelte_exports = {};
22
22
  __export(svelte_exports, {
23
- serve: () => serve
23
+ createWorkflow: () => createWorkflow,
24
+ serve: () => serve,
25
+ serveMany: () => serveMany
24
26
  });
25
27
  module.exports = __toCommonJS(svelte_exports);
26
28
 
@@ -83,12 +85,13 @@ var WORKFLOW_INIT_HEADER = "Upstash-Workflow-Init";
83
85
  var WORKFLOW_URL_HEADER = "Upstash-Workflow-Url";
84
86
  var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
85
87
  var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
88
+ var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
86
89
  var WORKFLOW_PROTOCOL_VERSION = "1";
87
90
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
88
91
  var DEFAULT_CONTENT_TYPE = "application/json";
89
92
  var NO_CONCURRENCY = 1;
90
93
  var DEFAULT_RETRIES = 3;
91
- var VERSION = "v0.2.3";
94
+ var VERSION = "v0.2.7";
92
95
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
93
96
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
94
97
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -135,6 +138,31 @@ var formatWorkflowError = (error) => {
135
138
  };
136
139
  };
137
140
 
141
+ // src/utils.ts
142
+ var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
143
+ var NANOID_LENGTH = 21;
144
+ function getRandomInt() {
145
+ return Math.floor(Math.random() * NANOID_CHARS.length);
146
+ }
147
+ function nanoid() {
148
+ return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
149
+ }
150
+ function getWorkflowRunId(id) {
151
+ return `wfr_${id ?? nanoid()}`;
152
+ }
153
+ function decodeBase64(base64) {
154
+ try {
155
+ const binString = atob(base64);
156
+ const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
157
+ return new TextDecoder().decode(intArray);
158
+ } catch (error) {
159
+ console.warn(
160
+ `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
161
+ );
162
+ return atob(base64);
163
+ }
164
+ }
165
+
138
166
  // src/context/steps.ts
139
167
  var BaseLazyStep = class {
140
168
  stepName;
@@ -239,8 +267,9 @@ var LazyCallStep = class extends BaseLazyStep {
239
267
  headers;
240
268
  retries;
241
269
  timeout;
270
+ flowControl;
242
271
  stepType = "Call";
243
- constructor(stepName, url, method, body, headers, retries, timeout) {
272
+ constructor(stepName, url, method, body, headers, retries, timeout, flowControl) {
244
273
  super(stepName);
245
274
  this.url = url;
246
275
  this.method = method;
@@ -248,6 +277,7 @@ var LazyCallStep = class extends BaseLazyStep {
248
277
  this.headers = headers;
249
278
  this.retries = retries;
250
279
  this.timeout = timeout;
280
+ this.flowControl = flowControl;
251
281
  }
252
282
  getPlanStep(concurrent, targetStep) {
253
283
  return {
@@ -315,6 +345,49 @@ var LazyNotifyStep = class extends LazyFunctionStep {
315
345
  });
316
346
  }
317
347
  };
348
+ var LazyInvokeStep = class extends BaseLazyStep {
349
+ stepType = "Invoke";
350
+ params;
351
+ constructor(stepName, {
352
+ workflow,
353
+ body,
354
+ headers = {},
355
+ workflowRunId,
356
+ retries,
357
+ flowControl
358
+ }) {
359
+ super(stepName);
360
+ this.params = {
361
+ workflow,
362
+ body,
363
+ headers,
364
+ workflowRunId: getWorkflowRunId(workflowRunId),
365
+ retries,
366
+ flowControl
367
+ };
368
+ }
369
+ getPlanStep(concurrent, targetStep) {
370
+ return {
371
+ stepId: 0,
372
+ stepName: this.stepName,
373
+ stepType: this.stepType,
374
+ concurrent,
375
+ targetStep
376
+ };
377
+ }
378
+ /**
379
+ * won't be used as it's the server who will add the result step
380
+ * in Invoke step.
381
+ */
382
+ getResultStep(concurrent, stepId) {
383
+ return Promise.resolve({
384
+ stepId,
385
+ stepName: this.stepName,
386
+ stepType: this.stepType,
387
+ concurrent
388
+ });
389
+ }
390
+ };
318
391
 
319
392
  // node_modules/neverthrow/dist/index.es.js
320
393
  var defaultErrorConfig = {
@@ -739,7 +812,8 @@ var StepTypes = [
739
812
  "SleepUntil",
740
813
  "Call",
741
814
  "Wait",
742
- "Notify"
815
+ "Notify",
816
+ "Invoke"
743
817
  ];
744
818
 
745
819
  // src/workflow-requests.ts
@@ -747,8 +821,9 @@ var import_qstash3 = require("@upstash/qstash");
747
821
  var triggerFirstInvocation = async ({
748
822
  workflowContext,
749
823
  useJSONContent,
750
- telemetry,
751
- debug
824
+ telemetry: telemetry2,
825
+ debug,
826
+ invokeCount
752
827
  }) => {
753
828
  const { headers } = getHeaders({
754
829
  initHeaderValue: "true",
@@ -757,7 +832,9 @@ var triggerFirstInvocation = async ({
757
832
  userHeaders: workflowContext.headers,
758
833
  failureUrl: workflowContext.failureUrl,
759
834
  retries: workflowContext.retries,
760
- telemetry
835
+ telemetry: telemetry2,
836
+ invokeCount,
837
+ flowControl: workflowContext.flowControl
761
838
  });
762
839
  if (workflowContext.headers.get("content-type")) {
763
840
  headers["content-type"] = workflowContext.headers.get("content-type");
@@ -803,8 +880,8 @@ var triggerRouteFunction = async ({
803
880
  debug
804
881
  }) => {
805
882
  try {
806
- await onStep();
807
- await onCleanup();
883
+ const result = await onStep();
884
+ await onCleanup(result);
808
885
  return ok("workflow-finished");
809
886
  } catch (error) {
810
887
  const error_ = error;
@@ -825,14 +902,15 @@ var triggerRouteFunction = async ({
825
902
  }
826
903
  }
827
904
  };
828
- var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
905
+ var triggerWorkflowDelete = async (workflowContext, result, debug, cancel = false) => {
829
906
  await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
830
907
  deletedWorkflowRunId: workflowContext.workflowRunId
831
908
  });
832
909
  await workflowContext.qstashClient.http.request({
833
910
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
834
911
  method: "DELETE",
835
- parseResponseAsJson: false
912
+ parseResponseAsJson: false,
913
+ body: JSON.stringify(result)
836
914
  });
837
915
  await debug?.log(
838
916
  "SUBMIT",
@@ -861,7 +939,8 @@ var handleThirdPartyCallResult = async ({
861
939
  workflowUrl,
862
940
  failureUrl,
863
941
  retries,
864
- telemetry,
942
+ telemetry: telemetry2,
943
+ flowControl,
865
944
  debug
866
945
  }) => {
867
946
  try {
@@ -909,6 +988,7 @@ ${atob(callbackMessage.body ?? "")}`
909
988
  const stepType = request.headers.get("Upstash-Workflow-StepType");
910
989
  const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
911
990
  const contentType = request.headers.get("Upstash-Workflow-ContentType");
991
+ const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
912
992
  if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
913
993
  throw new Error(
914
994
  `Missing info in callback message source header: ${JSON.stringify({
@@ -929,7 +1009,9 @@ ${atob(callbackMessage.body ?? "")}`
929
1009
  userHeaders,
930
1010
  failureUrl,
931
1011
  retries,
932
- telemetry
1012
+ telemetry: telemetry2,
1013
+ invokeCount: Number(invokeCount),
1014
+ flowControl
933
1015
  });
934
1016
  const callResponse = {
935
1017
  status: callbackMessage.status,
@@ -968,11 +1050,11 @@ ${atob(callbackMessage.body ?? "")}`
968
1050
  );
969
1051
  }
970
1052
  };
971
- var getTelemetryHeaders = (telemetry) => {
1053
+ var getTelemetryHeaders = (telemetry2) => {
972
1054
  return {
973
- [TELEMETRY_HEADER_SDK]: telemetry.sdk,
974
- [TELEMETRY_HEADER_FRAMEWORK]: telemetry.framework,
975
- [TELEMETRY_HEADER_RUNTIME]: telemetry.runtime ?? "unknown"
1055
+ [TELEMETRY_HEADER_SDK]: telemetry2.sdk,
1056
+ [TELEMETRY_HEADER_FRAMEWORK]: telemetry2.framework,
1057
+ [TELEMETRY_HEADER_RUNTIME]: telemetry2.runtime ?? "unknown"
976
1058
  };
977
1059
  };
978
1060
  var getHeaders = ({
@@ -985,15 +1067,24 @@ var getHeaders = ({
985
1067
  step,
986
1068
  callRetries,
987
1069
  callTimeout,
988
- telemetry
1070
+ telemetry: telemetry2,
1071
+ invokeCount,
1072
+ flowControl,
1073
+ callFlowControl
989
1074
  }) => {
1075
+ const contentType = (userHeaders ? userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
990
1076
  const baseHeaders = {
991
1077
  [WORKFLOW_INIT_HEADER]: initHeaderValue,
992
1078
  [WORKFLOW_ID_HEADER]: workflowRunId,
993
1079
  [WORKFLOW_URL_HEADER]: workflowUrl,
994
1080
  [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody",
995
- ...telemetry ? getTelemetryHeaders(telemetry) : {}
1081
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1082
+ "content-type": contentType,
1083
+ ...telemetry2 ? getTelemetryHeaders(telemetry2) : {}
996
1084
  };
1085
+ if (invokeCount !== void 0 && !step?.callUrl) {
1086
+ baseHeaders[`Upstash-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`] = invokeCount.toString();
1087
+ }
997
1088
  if (!step?.callUrl) {
998
1089
  baseHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
999
1090
  }
@@ -1010,6 +1101,11 @@ var getHeaders = ({
1010
1101
  if (retries !== void 0) {
1011
1102
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1012
1103
  }
1104
+ if (flowControl) {
1105
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1106
+ baseHeaders["Upstash-Failure-Callback-Flow-Control-Key"] = flowControlKey;
1107
+ baseHeaders["Upstash-Failure-Callback-Flow-Control-Value"] = flowControlValue;
1108
+ }
1013
1109
  if (!step?.callUrl) {
1014
1110
  baseHeaders["Upstash-Failure-Callback"] = failureUrl;
1015
1111
  }
@@ -1021,9 +1117,26 @@ var getHeaders = ({
1021
1117
  baseHeaders["Upstash-Callback-Retries"] = retries.toString();
1022
1118
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1023
1119
  }
1024
- } else if (retries !== void 0) {
1025
- baseHeaders["Upstash-Retries"] = retries.toString();
1026
- baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1120
+ if (callFlowControl) {
1121
+ const { flowControlKey, flowControlValue } = prepareFlowControl(callFlowControl);
1122
+ baseHeaders["Upstash-Flow-Control-Key"] = flowControlKey;
1123
+ baseHeaders["Upstash-Flow-Control-Value"] = flowControlValue;
1124
+ }
1125
+ if (flowControl) {
1126
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1127
+ baseHeaders["Upstash-Callback-Flow-Control-Key"] = flowControlKey;
1128
+ baseHeaders["Upstash-Callback-Flow-Control-Value"] = flowControlValue;
1129
+ }
1130
+ } else {
1131
+ if (flowControl) {
1132
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1133
+ baseHeaders["Upstash-Flow-Control-Key"] = flowControlKey;
1134
+ baseHeaders["Upstash-Flow-Control-Value"] = flowControlValue;
1135
+ }
1136
+ if (retries !== void 0) {
1137
+ baseHeaders["Upstash-Retries"] = retries.toString();
1138
+ baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1139
+ }
1027
1140
  }
1028
1141
  if (userHeaders) {
1029
1142
  for (const header of userHeaders.keys()) {
@@ -1035,7 +1148,6 @@ var getHeaders = ({
1035
1148
  baseHeaders[`Upstash-Failure-Callback-Forward-${header}`] = userHeaders.get(header);
1036
1149
  }
1037
1150
  }
1038
- const contentType = (userHeaders ? userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
1039
1151
  if (step?.callHeaders) {
1040
1152
  const forwardedHeaders = Object.fromEntries(
1041
1153
  Object.entries(step.callHeaders).map(([header, value]) => [
@@ -1059,6 +1171,7 @@ var getHeaders = ({
1059
1171
  "Upstash-Callback-Forward-Upstash-Workflow-StepType": step.stepType,
1060
1172
  "Upstash-Callback-Forward-Upstash-Workflow-Concurrent": step.concurrent.toString(),
1061
1173
  "Upstash-Callback-Forward-Upstash-Workflow-ContentType": contentType,
1174
+ [`Upstash-Callback-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`]: (invokeCount ?? 0).toString(),
1062
1175
  "Upstash-Workflow-CallType": "toCallback"
1063
1176
  }
1064
1177
  };
@@ -1075,8 +1188,8 @@ var getHeaders = ({
1075
1188
  Object.entries(baseHeaders).map(([header, value]) => [header, [value]])
1076
1189
  ),
1077
1190
  // to include telemetry headers:
1078
- ...telemetry ? Object.fromEntries(
1079
- Object.entries(getTelemetryHeaders(telemetry)).map(([header, value]) => [
1191
+ ...telemetry2 ? Object.fromEntries(
1192
+ Object.entries(getTelemetryHeaders(telemetry2)).map(([header, value]) => [
1080
1193
  header,
1081
1194
  [value]
1082
1195
  ])
@@ -1085,8 +1198,7 @@ var getHeaders = ({
1085
1198
  "Upstash-Workflow-Runid": [workflowRunId],
1086
1199
  [WORKFLOW_INIT_HEADER]: ["false"],
1087
1200
  [WORKFLOW_URL_HEADER]: [workflowUrl],
1088
- "Upstash-Workflow-CallType": ["step"],
1089
- "Content-Type": [contentType]
1201
+ "Upstash-Workflow-CallType": ["step"]
1090
1202
  }
1091
1203
  };
1092
1204
  }
@@ -1117,9 +1229,159 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
1117
1229
  );
1118
1230
  }
1119
1231
  };
1232
+ var prepareFlowControl = (flowControl) => {
1233
+ const parallelism = flowControl.parallelism?.toString();
1234
+ const rate = flowControl.ratePerSecond?.toString();
1235
+ const controlValue = [
1236
+ parallelism ? `parallelism=${parallelism}` : void 0,
1237
+ rate ? `rate=${rate}` : void 0
1238
+ ].filter(Boolean);
1239
+ if (controlValue.length === 0) {
1240
+ throw new import_qstash3.QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
1241
+ }
1242
+ return {
1243
+ flowControlKey: flowControl.key,
1244
+ flowControlValue: controlValue.join(", ")
1245
+ };
1246
+ };
1120
1247
 
1121
1248
  // src/context/auto-executor.ts
1122
1249
  var import_qstash4 = require("@upstash/qstash");
1250
+
1251
+ // src/serve/serve-many.ts
1252
+ var getWorkflowId = (url) => {
1253
+ const components = url.split("/");
1254
+ const lastComponent = components[components.length - 1];
1255
+ return lastComponent.split("?")[0];
1256
+ };
1257
+ var serveManyBase = ({
1258
+ workflows,
1259
+ getUrl,
1260
+ serveMethod,
1261
+ options
1262
+ }) => {
1263
+ const workflowIds = [];
1264
+ const workflowMap = Object.fromEntries(
1265
+ Object.entries(workflows).map((workflow) => {
1266
+ const workflowId = workflow[0];
1267
+ if (workflowIds.includes(workflowId)) {
1268
+ throw new WorkflowError(
1269
+ `Duplicate workflow name found: '${workflowId}'. Please set different workflow names in serveMany.`
1270
+ );
1271
+ }
1272
+ if (workflowId.includes("/")) {
1273
+ throw new WorkflowError(
1274
+ `Invalid workflow name found: '${workflowId}'. Workflow name cannot contain '/'.`
1275
+ );
1276
+ }
1277
+ workflowIds.push(workflowId);
1278
+ workflow[1].workflowId = workflowId;
1279
+ workflow[1].options = {
1280
+ ...options,
1281
+ ...workflow[1].options
1282
+ };
1283
+ const params = [workflow[1].routeFunction, workflow[1].options];
1284
+ const handler = serveMethod(...params);
1285
+ return [workflowId, handler];
1286
+ })
1287
+ );
1288
+ return {
1289
+ handler: async (...params) => {
1290
+ const url = getUrl(...params);
1291
+ const pickedWorkflowId = getWorkflowId(url);
1292
+ if (!pickedWorkflowId) {
1293
+ return new Response(
1294
+ `Unexpected request in serveMany. workflowId not set. Please update the URL of your request.`,
1295
+ {
1296
+ status: 404
1297
+ }
1298
+ );
1299
+ }
1300
+ const workflow = workflowMap[pickedWorkflowId];
1301
+ if (!workflow) {
1302
+ return new Response(
1303
+ `No workflows in serveMany found for '${pickedWorkflowId}'. Please update the URL of your request.`,
1304
+ {
1305
+ status: 404
1306
+ }
1307
+ );
1308
+ }
1309
+ return await workflow(...params);
1310
+ }
1311
+ };
1312
+ };
1313
+ var invokeWorkflow = async ({
1314
+ settings,
1315
+ invokeStep,
1316
+ context,
1317
+ invokeCount,
1318
+ telemetry: telemetry2
1319
+ }) => {
1320
+ const {
1321
+ body,
1322
+ workflow,
1323
+ headers = {},
1324
+ workflowRunId = getWorkflowRunId(),
1325
+ retries,
1326
+ flowControl
1327
+ } = settings;
1328
+ const { workflowId } = workflow;
1329
+ const {
1330
+ retries: workflowRetries,
1331
+ failureFunction,
1332
+ failureUrl,
1333
+ useJSONContent,
1334
+ flowControl: workflowFlowControl
1335
+ } = workflow.options;
1336
+ if (!workflowId) {
1337
+ throw new WorkflowError("You can only invoke workflow which has a workflowId");
1338
+ }
1339
+ const { headers: invokerHeaders } = getHeaders({
1340
+ initHeaderValue: "false",
1341
+ workflowRunId: context.workflowRunId,
1342
+ workflowUrl: context.url,
1343
+ userHeaders: context.headers,
1344
+ failureUrl: context.failureUrl,
1345
+ retries: context.retries,
1346
+ telemetry: telemetry2,
1347
+ invokeCount,
1348
+ flowControl: context.flowControl
1349
+ });
1350
+ invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
1351
+ const newUrl = context.url.replace(/[^/]+$/, workflowId);
1352
+ const { headers: triggerHeaders } = getHeaders({
1353
+ initHeaderValue: "true",
1354
+ workflowRunId,
1355
+ workflowUrl: newUrl,
1356
+ userHeaders: new Headers(headers),
1357
+ retries: retries ?? workflowRetries,
1358
+ telemetry: telemetry2,
1359
+ failureUrl: failureFunction ? newUrl : failureUrl,
1360
+ invokeCount: invokeCount + 1,
1361
+ flowControl: flowControl ?? workflowFlowControl
1362
+ });
1363
+ triggerHeaders["Upstash-Workflow-Invoke"] = "true";
1364
+ if (useJSONContent) {
1365
+ triggerHeaders["content-type"] = "application/json";
1366
+ }
1367
+ const request = {
1368
+ body: JSON.stringify(body),
1369
+ headers: Object.fromEntries(
1370
+ Object.entries(invokerHeaders).map((pairs) => [pairs[0], [pairs[1]]])
1371
+ ),
1372
+ workflowRunId,
1373
+ workflowUrl: context.url,
1374
+ step: invokeStep
1375
+ };
1376
+ await context.qstashClient.publish({
1377
+ headers: triggerHeaders,
1378
+ method: "POST",
1379
+ body: JSON.stringify(request),
1380
+ url: newUrl
1381
+ });
1382
+ };
1383
+
1384
+ // src/context/auto-executor.ts
1123
1385
  var AutoExecutor = class _AutoExecutor {
1124
1386
  context;
1125
1387
  promises = /* @__PURE__ */ new WeakMap();
@@ -1128,14 +1390,16 @@ var AutoExecutor = class _AutoExecutor {
1128
1390
  nonPlanStepCount;
1129
1391
  steps;
1130
1392
  indexInCurrentList = 0;
1393
+ invokeCount;
1131
1394
  telemetry;
1132
1395
  stepCount = 0;
1133
1396
  planStepCount = 0;
1134
1397
  executingStep = false;
1135
- constructor(context, steps, telemetry, debug) {
1398
+ constructor(context, steps, telemetry2, invokeCount, debug) {
1136
1399
  this.context = context;
1137
1400
  this.steps = steps;
1138
- this.telemetry = telemetry;
1401
+ this.telemetry = telemetry2;
1402
+ this.invokeCount = invokeCount ?? 0;
1139
1403
  this.debug = debug;
1140
1404
  this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
1141
1405
  }
@@ -1358,7 +1622,9 @@ var AutoExecutor = class _AutoExecutor {
1358
1622
  step: waitStep,
1359
1623
  failureUrl: this.context.failureUrl,
1360
1624
  retries: this.context.retries,
1361
- telemetry: this.telemetry
1625
+ telemetry: this.telemetry,
1626
+ invokeCount: this.invokeCount,
1627
+ flowControl: this.context.flowControl
1362
1628
  });
1363
1629
  const waitBody = {
1364
1630
  url: this.context.url,
@@ -1381,7 +1647,19 @@ var AutoExecutor = class _AutoExecutor {
1381
1647
  method: "POST",
1382
1648
  parseResponseAsJson: false
1383
1649
  });
1384
- throw new WorkflowAbort(steps[0].stepName, steps[0]);
1650
+ throw new WorkflowAbort(waitStep.stepName, waitStep);
1651
+ }
1652
+ if (steps.length === 1 && lazySteps[0] instanceof LazyInvokeStep) {
1653
+ const invokeStep = steps[0];
1654
+ const lazyInvokeStep = lazySteps[0];
1655
+ await invokeWorkflow({
1656
+ settings: lazyInvokeStep.params,
1657
+ invokeStep,
1658
+ context: this.context,
1659
+ invokeCount: this.invokeCount,
1660
+ telemetry: this.telemetry
1661
+ });
1662
+ throw new WorkflowAbort(invokeStep.stepName, invokeStep);
1385
1663
  }
1386
1664
  const result = await this.context.qstashClient.batchJSON(
1387
1665
  steps.map((singleStep, index) => {
@@ -1396,11 +1674,14 @@ var AutoExecutor = class _AutoExecutor {
1396
1674
  retries: this.context.retries,
1397
1675
  callRetries: lazyStep instanceof LazyCallStep ? lazyStep.retries : void 0,
1398
1676
  callTimeout: lazyStep instanceof LazyCallStep ? lazyStep.timeout : void 0,
1399
- telemetry: this.telemetry
1677
+ telemetry: this.telemetry,
1678
+ invokeCount: this.invokeCount,
1679
+ flowControl: this.context.flowControl,
1680
+ callFlowControl: lazyStep instanceof LazyCallStep ? lazyStep.flowControl : void 0
1400
1681
  });
1401
1682
  const willWait = singleStep.concurrent === NO_CONCURRENCY || singleStep.stepId === 0;
1402
1683
  singleStep.out = JSON.stringify(singleStep.out);
1403
- return singleStep.callUrl ? (
1684
+ return singleStep.callUrl && lazyStep instanceof LazyCallStep ? (
1404
1685
  // if the step is a third party call, we call the third party
1405
1686
  // url (singleStep.callUrl) and pass information about the workflow
1406
1687
  // in the headers (handled in getHeaders). QStash makes the request
@@ -2057,6 +2338,11 @@ var WorkflowContext = class {
2057
2338
  * Number of retries
2058
2339
  */
2059
2340
  retries;
2341
+ /**
2342
+ * Settings for controlling the number of active requests
2343
+ * and number of requests per second with the same key.
2344
+ */
2345
+ flowControl;
2060
2346
  constructor({
2061
2347
  qstashClient,
2062
2348
  workflowRunId,
@@ -2068,7 +2354,9 @@ var WorkflowContext = class {
2068
2354
  initialPayload,
2069
2355
  env,
2070
2356
  retries,
2071
- telemetry
2357
+ telemetry: telemetry2,
2358
+ invokeCount,
2359
+ flowControl
2072
2360
  }) {
2073
2361
  this.qstashClient = qstashClient;
2074
2362
  this.workflowRunId = workflowRunId;
@@ -2079,7 +2367,8 @@ var WorkflowContext = class {
2079
2367
  this.requestPayload = initialPayload;
2080
2368
  this.env = env ?? {};
2081
2369
  this.retries = retries ?? DEFAULT_RETRIES;
2082
- this.executor = new AutoExecutor(this, this.steps, telemetry, debug);
2370
+ this.flowControl = flowControl;
2371
+ this.executor = new AutoExecutor(this, this.steps, telemetry2, invokeCount, debug);
2083
2372
  }
2084
2373
  /**
2085
2374
  * Executes a workflow step
@@ -2181,7 +2470,7 @@ var WorkflowContext = class {
2181
2470
  * }
2182
2471
  */
2183
2472
  async call(stepName, settings) {
2184
- const { url, method = "GET", body, headers = {}, retries = 0, timeout } = settings;
2473
+ const { url, method = "GET", body, headers = {}, retries = 0, timeout, flowControl } = settings;
2185
2474
  const result = await this.addStep(
2186
2475
  new LazyCallStep(
2187
2476
  stepName,
@@ -2190,7 +2479,8 @@ var WorkflowContext = class {
2190
2479
  body,
2191
2480
  headers,
2192
2481
  retries,
2193
- timeout
2482
+ timeout,
2483
+ flowControl
2194
2484
  )
2195
2485
  );
2196
2486
  if (typeof result === "string") {
@@ -2299,6 +2589,13 @@ var WorkflowContext = class {
2299
2589
  return result;
2300
2590
  }
2301
2591
  }
2592
+ async invoke(stepName, settings) {
2593
+ const result = await this.addStep(new LazyInvokeStep(stepName, settings));
2594
+ return {
2595
+ ...result,
2596
+ body: result.body ? JSON.parse(result.body) : void 0
2597
+ };
2598
+ }
2302
2599
  /**
2303
2600
  * Cancel the current workflow run
2304
2601
  *
@@ -2376,31 +2673,6 @@ var WorkflowLogger = class _WorkflowLogger {
2376
2673
  }
2377
2674
  };
2378
2675
 
2379
- // src/utils.ts
2380
- var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
2381
- var NANOID_LENGTH = 21;
2382
- function getRandomInt() {
2383
- return Math.floor(Math.random() * NANOID_CHARS.length);
2384
- }
2385
- function nanoid() {
2386
- return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
2387
- }
2388
- function getWorkflowRunId(id) {
2389
- return `wfr_${id ?? nanoid()}`;
2390
- }
2391
- function decodeBase64(base64) {
2392
- try {
2393
- const binString = atob(base64);
2394
- const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
2395
- return new TextDecoder().decode(intArray);
2396
- } catch (error) {
2397
- console.warn(
2398
- `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
2399
- );
2400
- return atob(base64);
2401
- }
2402
- }
2403
-
2404
2676
  // src/serve/authorization.ts
2405
2677
  var import_qstash8 = require("@upstash/qstash");
2406
2678
  var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
@@ -2445,7 +2717,8 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
2445
2717
  failureUrl: context.failureUrl,
2446
2718
  initialPayload: context.requestPayload,
2447
2719
  env: context.env,
2448
- retries: context.retries
2720
+ retries: context.retries,
2721
+ flowControl: context.flowControl
2449
2722
  });
2450
2723
  try {
2451
2724
  await routeFunction(disabledContext);
@@ -2598,7 +2871,7 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
2598
2871
  };
2599
2872
  }
2600
2873
  };
2601
- var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, debug) => {
2874
+ var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, flowControl, debug) => {
2602
2875
  if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
2603
2876
  return ok("not-failure-callback");
2604
2877
  }
@@ -2610,22 +2883,21 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
2610
2883
  );
2611
2884
  }
2612
2885
  try {
2613
- const { status, header, body, url, sourceHeader, sourceBody, workflowRunId } = JSON.parse(
2614
- requestPayload
2615
- );
2886
+ const { status, header, body, url, sourceBody, workflowRunId } = JSON.parse(requestPayload);
2616
2887
  const decodedBody = body ? decodeBase64(body) : "{}";
2617
2888
  const errorPayload = JSON.parse(decodedBody);
2618
2889
  const workflowContext = new WorkflowContext({
2619
2890
  qstashClient,
2620
2891
  workflowRunId,
2621
2892
  initialPayload: sourceBody ? initialPayloadParser(decodeBase64(sourceBody)) : void 0,
2622
- headers: recreateUserHeaders(new Headers(sourceHeader)),
2893
+ headers: recreateUserHeaders(request.headers),
2623
2894
  steps: [],
2624
2895
  url,
2625
2896
  failureUrl: url,
2626
2897
  debug,
2627
2898
  env,
2628
2899
  retries,
2900
+ flowControl,
2629
2901
  telemetry: void 0
2630
2902
  // not going to make requests in authentication check
2631
2903
  });
@@ -2738,7 +3010,7 @@ var determineUrls = async (request, url, baseUrl, failureFunction, failureUrl, d
2738
3010
  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`;
2739
3011
 
2740
3012
  // src/serve/index.ts
2741
- var serveBase = (routeFunction, telemetry, options) => {
3013
+ var serveBase = (routeFunction, telemetry2, options) => {
2742
3014
  const {
2743
3015
  qstashClient,
2744
3016
  onStepFinish,
@@ -2752,9 +3024,10 @@ var serveBase = (routeFunction, telemetry, options) => {
2752
3024
  env,
2753
3025
  retries,
2754
3026
  useJSONContent,
2755
- disableTelemetry
3027
+ disableTelemetry,
3028
+ flowControl
2756
3029
  } = processOptions(options);
2757
- telemetry = disableTelemetry ? void 0 : telemetry;
3030
+ telemetry2 = disableTelemetry ? void 0 : telemetry2;
2758
3031
  const debug = WorkflowLogger.getLogger(verbose);
2759
3032
  const handler = async (request) => {
2760
3033
  await debug?.log("INFO", "ENDPOINT_START");
@@ -2793,6 +3066,7 @@ var serveBase = (routeFunction, telemetry, options) => {
2793
3066
  failureFunction,
2794
3067
  env,
2795
3068
  retries,
3069
+ flowControl,
2796
3070
  debug
2797
3071
  );
2798
3072
  if (failureCheck.isErr()) {
@@ -2801,6 +3075,7 @@ var serveBase = (routeFunction, telemetry, options) => {
2801
3075
  await debug?.log("WARN", "RESPONSE_DEFAULT", "failureFunction executed");
2802
3076
  return onStepFinish(workflowRunId, "failure-callback");
2803
3077
  }
3078
+ const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
2804
3079
  const workflowContext = new WorkflowContext({
2805
3080
  qstashClient,
2806
3081
  workflowRunId,
@@ -2812,7 +3087,9 @@ var serveBase = (routeFunction, telemetry, options) => {
2812
3087
  debug,
2813
3088
  env,
2814
3089
  retries,
2815
- telemetry
3090
+ telemetry: telemetry2,
3091
+ invokeCount,
3092
+ flowControl
2816
3093
  });
2817
3094
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
2818
3095
  routeFunction,
@@ -2835,7 +3112,8 @@ var serveBase = (routeFunction, telemetry, options) => {
2835
3112
  workflowUrl,
2836
3113
  failureUrl: workflowFailureUrl,
2837
3114
  retries,
2838
- telemetry,
3115
+ flowControl,
3116
+ telemetry: telemetry2,
2839
3117
  debug
2840
3118
  });
2841
3119
  if (callReturnCheck.isErr()) {
@@ -2844,10 +3122,16 @@ var serveBase = (routeFunction, telemetry, options) => {
2844
3122
  });
2845
3123
  throw callReturnCheck.error;
2846
3124
  } else if (callReturnCheck.value === "continue-workflow") {
2847
- const result = isFirstInvocation ? await triggerFirstInvocation({ workflowContext, useJSONContent, telemetry, debug }) : await triggerRouteFunction({
3125
+ const result = isFirstInvocation ? await triggerFirstInvocation({
3126
+ workflowContext,
3127
+ useJSONContent,
3128
+ telemetry: telemetry2,
3129
+ debug,
3130
+ invokeCount
3131
+ }) : await triggerRouteFunction({
2848
3132
  onStep: async () => routeFunction(workflowContext),
2849
- onCleanup: async () => {
2850
- await triggerWorkflowDelete(workflowContext, debug);
3133
+ onCleanup: async (result2) => {
3134
+ await triggerWorkflowDelete(workflowContext, result2, debug);
2851
3135
  },
2852
3136
  onCancel: async () => {
2853
3137
  await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
@@ -2880,24 +3164,43 @@ var serveBase = (routeFunction, telemetry, options) => {
2880
3164
  };
2881
3165
 
2882
3166
  // platforms/svelte.ts
3167
+ var telemetry = {
3168
+ sdk: SDK_TELEMETRY,
3169
+ framework: "svelte"
3170
+ };
2883
3171
  var serve = (routeFunction, options) => {
2884
3172
  const handler = async ({ request }) => {
2885
- const { handler: serveHandler } = serveBase(
2886
- routeFunction,
2887
- {
2888
- sdk: SDK_TELEMETRY,
2889
- framework: "svelte"
2890
- },
2891
- {
2892
- ...options,
2893
- useJSONContent: true
2894
- }
2895
- );
3173
+ const { handler: serveHandler } = serveBase(routeFunction, telemetry, {
3174
+ ...options,
3175
+ useJSONContent: true
3176
+ });
2896
3177
  return await serveHandler(request);
2897
3178
  };
2898
3179
  return { POST: handler };
2899
3180
  };
3181
+ var createWorkflow = (...params) => {
3182
+ const [routeFunction, options = {}] = params;
3183
+ return {
3184
+ workflowId: void 0,
3185
+ routeFunction,
3186
+ options
3187
+ };
3188
+ };
3189
+ var serveMany = (workflows, options) => {
3190
+ return {
3191
+ POST: serveManyBase({
3192
+ workflows,
3193
+ getUrl(params) {
3194
+ return params.url.toString();
3195
+ },
3196
+ options,
3197
+ serveMethod: (...params) => serve(...params).POST
3198
+ }).handler
3199
+ };
3200
+ };
2900
3201
  // Annotate the CommonJS export names for ESM import in node:
2901
3202
  0 && (module.exports = {
2902
- serve
3203
+ createWorkflow,
3204
+ serve,
3205
+ serveMany
2903
3206
  });