@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/index.js CHANGED
@@ -97,12 +97,13 @@ var WORKFLOW_INIT_HEADER = "Upstash-Workflow-Init";
97
97
  var WORKFLOW_URL_HEADER = "Upstash-Workflow-Url";
98
98
  var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
99
99
  var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
100
+ var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
100
101
  var WORKFLOW_PROTOCOL_VERSION = "1";
101
102
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
102
103
  var DEFAULT_CONTENT_TYPE = "application/json";
103
104
  var NO_CONCURRENCY = 1;
104
105
  var DEFAULT_RETRIES = 3;
105
- var VERSION = "v0.2.3";
106
+ var VERSION = "v0.2.7";
106
107
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
107
108
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
108
109
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -149,6 +150,31 @@ var formatWorkflowError = (error) => {
149
150
  };
150
151
  };
151
152
 
153
+ // src/utils.ts
154
+ var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
155
+ var NANOID_LENGTH = 21;
156
+ function getRandomInt() {
157
+ return Math.floor(Math.random() * NANOID_CHARS.length);
158
+ }
159
+ function nanoid() {
160
+ return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
161
+ }
162
+ function getWorkflowRunId(id) {
163
+ return `wfr_${id ?? nanoid()}`;
164
+ }
165
+ function decodeBase64(base64) {
166
+ try {
167
+ const binString = atob(base64);
168
+ const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
169
+ return new TextDecoder().decode(intArray);
170
+ } catch (error) {
171
+ console.warn(
172
+ `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
173
+ );
174
+ return atob(base64);
175
+ }
176
+ }
177
+
152
178
  // src/context/steps.ts
153
179
  var BaseLazyStep = class {
154
180
  stepName;
@@ -253,8 +279,9 @@ var LazyCallStep = class extends BaseLazyStep {
253
279
  headers;
254
280
  retries;
255
281
  timeout;
282
+ flowControl;
256
283
  stepType = "Call";
257
- constructor(stepName, url, method, body, headers, retries, timeout) {
284
+ constructor(stepName, url, method, body, headers, retries, timeout, flowControl) {
258
285
  super(stepName);
259
286
  this.url = url;
260
287
  this.method = method;
@@ -262,6 +289,7 @@ var LazyCallStep = class extends BaseLazyStep {
262
289
  this.headers = headers;
263
290
  this.retries = retries;
264
291
  this.timeout = timeout;
292
+ this.flowControl = flowControl;
265
293
  }
266
294
  getPlanStep(concurrent, targetStep) {
267
295
  return {
@@ -329,6 +357,49 @@ var LazyNotifyStep = class extends LazyFunctionStep {
329
357
  });
330
358
  }
331
359
  };
360
+ var LazyInvokeStep = class extends BaseLazyStep {
361
+ stepType = "Invoke";
362
+ params;
363
+ constructor(stepName, {
364
+ workflow,
365
+ body,
366
+ headers = {},
367
+ workflowRunId,
368
+ retries,
369
+ flowControl
370
+ }) {
371
+ super(stepName);
372
+ this.params = {
373
+ workflow,
374
+ body,
375
+ headers,
376
+ workflowRunId: getWorkflowRunId(workflowRunId),
377
+ retries,
378
+ flowControl
379
+ };
380
+ }
381
+ getPlanStep(concurrent, targetStep) {
382
+ return {
383
+ stepId: 0,
384
+ stepName: this.stepName,
385
+ stepType: this.stepType,
386
+ concurrent,
387
+ targetStep
388
+ };
389
+ }
390
+ /**
391
+ * won't be used as it's the server who will add the result step
392
+ * in Invoke step.
393
+ */
394
+ getResultStep(concurrent, stepId) {
395
+ return Promise.resolve({
396
+ stepId,
397
+ stepName: this.stepName,
398
+ stepType: this.stepType,
399
+ concurrent
400
+ });
401
+ }
402
+ };
332
403
 
333
404
  // node_modules/neverthrow/dist/index.es.js
334
405
  var defaultErrorConfig = {
@@ -753,7 +824,8 @@ var StepTypes = [
753
824
  "SleepUntil",
754
825
  "Call",
755
826
  "Wait",
756
- "Notify"
827
+ "Notify",
828
+ "Invoke"
757
829
  ];
758
830
 
759
831
  // src/workflow-requests.ts
@@ -762,7 +834,8 @@ var triggerFirstInvocation = async ({
762
834
  workflowContext,
763
835
  useJSONContent,
764
836
  telemetry,
765
- debug
837
+ debug,
838
+ invokeCount
766
839
  }) => {
767
840
  const { headers } = getHeaders({
768
841
  initHeaderValue: "true",
@@ -771,7 +844,9 @@ var triggerFirstInvocation = async ({
771
844
  userHeaders: workflowContext.headers,
772
845
  failureUrl: workflowContext.failureUrl,
773
846
  retries: workflowContext.retries,
774
- telemetry
847
+ telemetry,
848
+ invokeCount,
849
+ flowControl: workflowContext.flowControl
775
850
  });
776
851
  if (workflowContext.headers.get("content-type")) {
777
852
  headers["content-type"] = workflowContext.headers.get("content-type");
@@ -817,8 +892,8 @@ var triggerRouteFunction = async ({
817
892
  debug
818
893
  }) => {
819
894
  try {
820
- await onStep();
821
- await onCleanup();
895
+ const result = await onStep();
896
+ await onCleanup(result);
822
897
  return ok("workflow-finished");
823
898
  } catch (error) {
824
899
  const error_ = error;
@@ -839,14 +914,15 @@ var triggerRouteFunction = async ({
839
914
  }
840
915
  }
841
916
  };
842
- var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
917
+ var triggerWorkflowDelete = async (workflowContext, result, debug, cancel = false) => {
843
918
  await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
844
919
  deletedWorkflowRunId: workflowContext.workflowRunId
845
920
  });
846
921
  await workflowContext.qstashClient.http.request({
847
922
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
848
923
  method: "DELETE",
849
- parseResponseAsJson: false
924
+ parseResponseAsJson: false,
925
+ body: JSON.stringify(result)
850
926
  });
851
927
  await debug?.log(
852
928
  "SUBMIT",
@@ -876,6 +952,7 @@ var handleThirdPartyCallResult = async ({
876
952
  failureUrl,
877
953
  retries,
878
954
  telemetry,
955
+ flowControl,
879
956
  debug
880
957
  }) => {
881
958
  try {
@@ -923,6 +1000,7 @@ ${atob(callbackMessage.body ?? "")}`
923
1000
  const stepType = request.headers.get("Upstash-Workflow-StepType");
924
1001
  const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
925
1002
  const contentType = request.headers.get("Upstash-Workflow-ContentType");
1003
+ const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
926
1004
  if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
927
1005
  throw new Error(
928
1006
  `Missing info in callback message source header: ${JSON.stringify({
@@ -943,7 +1021,9 @@ ${atob(callbackMessage.body ?? "")}`
943
1021
  userHeaders,
944
1022
  failureUrl,
945
1023
  retries,
946
- telemetry
1024
+ telemetry,
1025
+ invokeCount: Number(invokeCount),
1026
+ flowControl
947
1027
  });
948
1028
  const callResponse = {
949
1029
  status: callbackMessage.status,
@@ -999,15 +1079,24 @@ var getHeaders = ({
999
1079
  step,
1000
1080
  callRetries,
1001
1081
  callTimeout,
1002
- telemetry
1082
+ telemetry,
1083
+ invokeCount,
1084
+ flowControl,
1085
+ callFlowControl
1003
1086
  }) => {
1087
+ const contentType = (userHeaders ? userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
1004
1088
  const baseHeaders = {
1005
1089
  [WORKFLOW_INIT_HEADER]: initHeaderValue,
1006
1090
  [WORKFLOW_ID_HEADER]: workflowRunId,
1007
1091
  [WORKFLOW_URL_HEADER]: workflowUrl,
1008
1092
  [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody",
1093
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1094
+ "content-type": contentType,
1009
1095
  ...telemetry ? getTelemetryHeaders(telemetry) : {}
1010
1096
  };
1097
+ if (invokeCount !== void 0 && !step?.callUrl) {
1098
+ baseHeaders[`Upstash-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`] = invokeCount.toString();
1099
+ }
1011
1100
  if (!step?.callUrl) {
1012
1101
  baseHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
1013
1102
  }
@@ -1024,6 +1113,11 @@ var getHeaders = ({
1024
1113
  if (retries !== void 0) {
1025
1114
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1026
1115
  }
1116
+ if (flowControl) {
1117
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1118
+ baseHeaders["Upstash-Failure-Callback-Flow-Control-Key"] = flowControlKey;
1119
+ baseHeaders["Upstash-Failure-Callback-Flow-Control-Value"] = flowControlValue;
1120
+ }
1027
1121
  if (!step?.callUrl) {
1028
1122
  baseHeaders["Upstash-Failure-Callback"] = failureUrl;
1029
1123
  }
@@ -1035,9 +1129,26 @@ var getHeaders = ({
1035
1129
  baseHeaders["Upstash-Callback-Retries"] = retries.toString();
1036
1130
  baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1037
1131
  }
1038
- } else if (retries !== void 0) {
1039
- baseHeaders["Upstash-Retries"] = retries.toString();
1040
- baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1132
+ if (callFlowControl) {
1133
+ const { flowControlKey, flowControlValue } = prepareFlowControl(callFlowControl);
1134
+ baseHeaders["Upstash-Flow-Control-Key"] = flowControlKey;
1135
+ baseHeaders["Upstash-Flow-Control-Value"] = flowControlValue;
1136
+ }
1137
+ if (flowControl) {
1138
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1139
+ baseHeaders["Upstash-Callback-Flow-Control-Key"] = flowControlKey;
1140
+ baseHeaders["Upstash-Callback-Flow-Control-Value"] = flowControlValue;
1141
+ }
1142
+ } else {
1143
+ if (flowControl) {
1144
+ const { flowControlKey, flowControlValue } = prepareFlowControl(flowControl);
1145
+ baseHeaders["Upstash-Flow-Control-Key"] = flowControlKey;
1146
+ baseHeaders["Upstash-Flow-Control-Value"] = flowControlValue;
1147
+ }
1148
+ if (retries !== void 0) {
1149
+ baseHeaders["Upstash-Retries"] = retries.toString();
1150
+ baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
1151
+ }
1041
1152
  }
1042
1153
  if (userHeaders) {
1043
1154
  for (const header of userHeaders.keys()) {
@@ -1049,7 +1160,6 @@ var getHeaders = ({
1049
1160
  baseHeaders[`Upstash-Failure-Callback-Forward-${header}`] = userHeaders.get(header);
1050
1161
  }
1051
1162
  }
1052
- const contentType = (userHeaders ? userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
1053
1163
  if (step?.callHeaders) {
1054
1164
  const forwardedHeaders = Object.fromEntries(
1055
1165
  Object.entries(step.callHeaders).map(([header, value]) => [
@@ -1073,6 +1183,7 @@ var getHeaders = ({
1073
1183
  "Upstash-Callback-Forward-Upstash-Workflow-StepType": step.stepType,
1074
1184
  "Upstash-Callback-Forward-Upstash-Workflow-Concurrent": step.concurrent.toString(),
1075
1185
  "Upstash-Callback-Forward-Upstash-Workflow-ContentType": contentType,
1186
+ [`Upstash-Callback-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`]: (invokeCount ?? 0).toString(),
1076
1187
  "Upstash-Workflow-CallType": "toCallback"
1077
1188
  }
1078
1189
  };
@@ -1099,8 +1210,7 @@ var getHeaders = ({
1099
1210
  "Upstash-Workflow-Runid": [workflowRunId],
1100
1211
  [WORKFLOW_INIT_HEADER]: ["false"],
1101
1212
  [WORKFLOW_URL_HEADER]: [workflowUrl],
1102
- "Upstash-Workflow-CallType": ["step"],
1103
- "Content-Type": [contentType]
1213
+ "Upstash-Workflow-CallType": ["step"]
1104
1214
  }
1105
1215
  };
1106
1216
  }
@@ -1131,9 +1241,98 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
1131
1241
  );
1132
1242
  }
1133
1243
  };
1244
+ var prepareFlowControl = (flowControl) => {
1245
+ const parallelism = flowControl.parallelism?.toString();
1246
+ const rate = flowControl.ratePerSecond?.toString();
1247
+ const controlValue = [
1248
+ parallelism ? `parallelism=${parallelism}` : void 0,
1249
+ rate ? `rate=${rate}` : void 0
1250
+ ].filter(Boolean);
1251
+ if (controlValue.length === 0) {
1252
+ throw new import_qstash3.QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
1253
+ }
1254
+ return {
1255
+ flowControlKey: flowControl.key,
1256
+ flowControlValue: controlValue.join(", ")
1257
+ };
1258
+ };
1134
1259
 
1135
1260
  // src/context/auto-executor.ts
1136
1261
  var import_qstash4 = require("@upstash/qstash");
1262
+
1263
+ // src/serve/serve-many.ts
1264
+ var invokeWorkflow = async ({
1265
+ settings,
1266
+ invokeStep,
1267
+ context,
1268
+ invokeCount,
1269
+ telemetry
1270
+ }) => {
1271
+ const {
1272
+ body,
1273
+ workflow,
1274
+ headers = {},
1275
+ workflowRunId = getWorkflowRunId(),
1276
+ retries,
1277
+ flowControl
1278
+ } = settings;
1279
+ const { workflowId } = workflow;
1280
+ const {
1281
+ retries: workflowRetries,
1282
+ failureFunction,
1283
+ failureUrl,
1284
+ useJSONContent,
1285
+ flowControl: workflowFlowControl
1286
+ } = workflow.options;
1287
+ if (!workflowId) {
1288
+ throw new WorkflowError("You can only invoke workflow which has a workflowId");
1289
+ }
1290
+ const { headers: invokerHeaders } = getHeaders({
1291
+ initHeaderValue: "false",
1292
+ workflowRunId: context.workflowRunId,
1293
+ workflowUrl: context.url,
1294
+ userHeaders: context.headers,
1295
+ failureUrl: context.failureUrl,
1296
+ retries: context.retries,
1297
+ telemetry,
1298
+ invokeCount,
1299
+ flowControl: context.flowControl
1300
+ });
1301
+ invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
1302
+ const newUrl = context.url.replace(/[^/]+$/, workflowId);
1303
+ const { headers: triggerHeaders } = getHeaders({
1304
+ initHeaderValue: "true",
1305
+ workflowRunId,
1306
+ workflowUrl: newUrl,
1307
+ userHeaders: new Headers(headers),
1308
+ retries: retries ?? workflowRetries,
1309
+ telemetry,
1310
+ failureUrl: failureFunction ? newUrl : failureUrl,
1311
+ invokeCount: invokeCount + 1,
1312
+ flowControl: flowControl ?? workflowFlowControl
1313
+ });
1314
+ triggerHeaders["Upstash-Workflow-Invoke"] = "true";
1315
+ if (useJSONContent) {
1316
+ triggerHeaders["content-type"] = "application/json";
1317
+ }
1318
+ const request = {
1319
+ body: JSON.stringify(body),
1320
+ headers: Object.fromEntries(
1321
+ Object.entries(invokerHeaders).map((pairs) => [pairs[0], [pairs[1]]])
1322
+ ),
1323
+ workflowRunId,
1324
+ workflowUrl: context.url,
1325
+ step: invokeStep
1326
+ };
1327
+ await context.qstashClient.publish({
1328
+ headers: triggerHeaders,
1329
+ method: "POST",
1330
+ body: JSON.stringify(request),
1331
+ url: newUrl
1332
+ });
1333
+ };
1334
+
1335
+ // src/context/auto-executor.ts
1137
1336
  var AutoExecutor = class _AutoExecutor {
1138
1337
  context;
1139
1338
  promises = /* @__PURE__ */ new WeakMap();
@@ -1142,14 +1341,16 @@ var AutoExecutor = class _AutoExecutor {
1142
1341
  nonPlanStepCount;
1143
1342
  steps;
1144
1343
  indexInCurrentList = 0;
1344
+ invokeCount;
1145
1345
  telemetry;
1146
1346
  stepCount = 0;
1147
1347
  planStepCount = 0;
1148
1348
  executingStep = false;
1149
- constructor(context, steps, telemetry, debug) {
1349
+ constructor(context, steps, telemetry, invokeCount, debug) {
1150
1350
  this.context = context;
1151
1351
  this.steps = steps;
1152
1352
  this.telemetry = telemetry;
1353
+ this.invokeCount = invokeCount ?? 0;
1153
1354
  this.debug = debug;
1154
1355
  this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
1155
1356
  }
@@ -1372,7 +1573,9 @@ var AutoExecutor = class _AutoExecutor {
1372
1573
  step: waitStep,
1373
1574
  failureUrl: this.context.failureUrl,
1374
1575
  retries: this.context.retries,
1375
- telemetry: this.telemetry
1576
+ telemetry: this.telemetry,
1577
+ invokeCount: this.invokeCount,
1578
+ flowControl: this.context.flowControl
1376
1579
  });
1377
1580
  const waitBody = {
1378
1581
  url: this.context.url,
@@ -1395,7 +1598,19 @@ var AutoExecutor = class _AutoExecutor {
1395
1598
  method: "POST",
1396
1599
  parseResponseAsJson: false
1397
1600
  });
1398
- throw new WorkflowAbort(steps[0].stepName, steps[0]);
1601
+ throw new WorkflowAbort(waitStep.stepName, waitStep);
1602
+ }
1603
+ if (steps.length === 1 && lazySteps[0] instanceof LazyInvokeStep) {
1604
+ const invokeStep = steps[0];
1605
+ const lazyInvokeStep = lazySteps[0];
1606
+ await invokeWorkflow({
1607
+ settings: lazyInvokeStep.params,
1608
+ invokeStep,
1609
+ context: this.context,
1610
+ invokeCount: this.invokeCount,
1611
+ telemetry: this.telemetry
1612
+ });
1613
+ throw new WorkflowAbort(invokeStep.stepName, invokeStep);
1399
1614
  }
1400
1615
  const result = await this.context.qstashClient.batchJSON(
1401
1616
  steps.map((singleStep, index) => {
@@ -1410,11 +1625,14 @@ var AutoExecutor = class _AutoExecutor {
1410
1625
  retries: this.context.retries,
1411
1626
  callRetries: lazyStep instanceof LazyCallStep ? lazyStep.retries : void 0,
1412
1627
  callTimeout: lazyStep instanceof LazyCallStep ? lazyStep.timeout : void 0,
1413
- telemetry: this.telemetry
1628
+ telemetry: this.telemetry,
1629
+ invokeCount: this.invokeCount,
1630
+ flowControl: this.context.flowControl,
1631
+ callFlowControl: lazyStep instanceof LazyCallStep ? lazyStep.flowControl : void 0
1414
1632
  });
1415
1633
  const willWait = singleStep.concurrent === NO_CONCURRENCY || singleStep.stepId === 0;
1416
1634
  singleStep.out = JSON.stringify(singleStep.out);
1417
- return singleStep.callUrl ? (
1635
+ return singleStep.callUrl && lazyStep instanceof LazyCallStep ? (
1418
1636
  // if the step is a third party call, we call the third party
1419
1637
  // url (singleStep.callUrl) and pass information about the workflow
1420
1638
  // in the headers (handled in getHeaders). QStash makes the request
@@ -1756,7 +1974,7 @@ var WorkflowTool = class {
1756
1974
  */
1757
1975
  executeAsStep;
1758
1976
  /**
1759
- *
1977
+ *
1760
1978
  * @param description description of the tool
1761
1979
  * @param schema schema of the tool
1762
1980
  * @param invoke function to invoke the tool
@@ -2102,6 +2320,11 @@ var WorkflowContext = class {
2102
2320
  * Number of retries
2103
2321
  */
2104
2322
  retries;
2323
+ /**
2324
+ * Settings for controlling the number of active requests
2325
+ * and number of requests per second with the same key.
2326
+ */
2327
+ flowControl;
2105
2328
  constructor({
2106
2329
  qstashClient,
2107
2330
  workflowRunId,
@@ -2113,7 +2336,9 @@ var WorkflowContext = class {
2113
2336
  initialPayload,
2114
2337
  env,
2115
2338
  retries,
2116
- telemetry
2339
+ telemetry,
2340
+ invokeCount,
2341
+ flowControl
2117
2342
  }) {
2118
2343
  this.qstashClient = qstashClient;
2119
2344
  this.workflowRunId = workflowRunId;
@@ -2124,7 +2349,8 @@ var WorkflowContext = class {
2124
2349
  this.requestPayload = initialPayload;
2125
2350
  this.env = env ?? {};
2126
2351
  this.retries = retries ?? DEFAULT_RETRIES;
2127
- this.executor = new AutoExecutor(this, this.steps, telemetry, debug);
2352
+ this.flowControl = flowControl;
2353
+ this.executor = new AutoExecutor(this, this.steps, telemetry, invokeCount, debug);
2128
2354
  }
2129
2355
  /**
2130
2356
  * Executes a workflow step
@@ -2226,7 +2452,7 @@ var WorkflowContext = class {
2226
2452
  * }
2227
2453
  */
2228
2454
  async call(stepName, settings) {
2229
- const { url, method = "GET", body, headers = {}, retries = 0, timeout } = settings;
2455
+ const { url, method = "GET", body, headers = {}, retries = 0, timeout, flowControl } = settings;
2230
2456
  const result = await this.addStep(
2231
2457
  new LazyCallStep(
2232
2458
  stepName,
@@ -2235,7 +2461,8 @@ var WorkflowContext = class {
2235
2461
  body,
2236
2462
  headers,
2237
2463
  retries,
2238
- timeout
2464
+ timeout,
2465
+ flowControl
2239
2466
  )
2240
2467
  );
2241
2468
  if (typeof result === "string") {
@@ -2344,6 +2571,13 @@ var WorkflowContext = class {
2344
2571
  return result;
2345
2572
  }
2346
2573
  }
2574
+ async invoke(stepName, settings) {
2575
+ const result = await this.addStep(new LazyInvokeStep(stepName, settings));
2576
+ return {
2577
+ ...result,
2578
+ body: result.body ? JSON.parse(result.body) : void 0
2579
+ };
2580
+ }
2347
2581
  /**
2348
2582
  * Cancel the current workflow run
2349
2583
  *
@@ -2421,31 +2655,6 @@ var WorkflowLogger = class _WorkflowLogger {
2421
2655
  }
2422
2656
  };
2423
2657
 
2424
- // src/utils.ts
2425
- var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
2426
- var NANOID_LENGTH = 21;
2427
- function getRandomInt() {
2428
- return Math.floor(Math.random() * NANOID_CHARS.length);
2429
- }
2430
- function nanoid() {
2431
- return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
2432
- }
2433
- function getWorkflowRunId(id) {
2434
- return `wfr_${id ?? nanoid()}`;
2435
- }
2436
- function decodeBase64(base64) {
2437
- try {
2438
- const binString = atob(base64);
2439
- const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
2440
- return new TextDecoder().decode(intArray);
2441
- } catch (error) {
2442
- console.warn(
2443
- `Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
2444
- );
2445
- return atob(base64);
2446
- }
2447
- }
2448
-
2449
2658
  // src/serve/authorization.ts
2450
2659
  var import_qstash8 = require("@upstash/qstash");
2451
2660
  var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
@@ -2490,7 +2699,8 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
2490
2699
  failureUrl: context.failureUrl,
2491
2700
  initialPayload: context.requestPayload,
2492
2701
  env: context.env,
2493
- retries: context.retries
2702
+ retries: context.retries,
2703
+ flowControl: context.flowControl
2494
2704
  });
2495
2705
  try {
2496
2706
  await routeFunction(disabledContext);
@@ -2643,7 +2853,7 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
2643
2853
  };
2644
2854
  }
2645
2855
  };
2646
- var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, debug) => {
2856
+ var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, flowControl, debug) => {
2647
2857
  if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
2648
2858
  return ok("not-failure-callback");
2649
2859
  }
@@ -2655,22 +2865,21 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
2655
2865
  );
2656
2866
  }
2657
2867
  try {
2658
- const { status, header, body, url, sourceHeader, sourceBody, workflowRunId } = JSON.parse(
2659
- requestPayload
2660
- );
2868
+ const { status, header, body, url, sourceBody, workflowRunId } = JSON.parse(requestPayload);
2661
2869
  const decodedBody = body ? decodeBase64(body) : "{}";
2662
2870
  const errorPayload = JSON.parse(decodedBody);
2663
2871
  const workflowContext = new WorkflowContext({
2664
2872
  qstashClient,
2665
2873
  workflowRunId,
2666
2874
  initialPayload: sourceBody ? initialPayloadParser(decodeBase64(sourceBody)) : void 0,
2667
- headers: recreateUserHeaders(new Headers(sourceHeader)),
2875
+ headers: recreateUserHeaders(request.headers),
2668
2876
  steps: [],
2669
2877
  url,
2670
2878
  failureUrl: url,
2671
2879
  debug,
2672
2880
  env,
2673
2881
  retries,
2882
+ flowControl,
2674
2883
  telemetry: void 0
2675
2884
  // not going to make requests in authentication check
2676
2885
  });
@@ -2797,7 +3006,8 @@ var serveBase = (routeFunction, telemetry, options) => {
2797
3006
  env,
2798
3007
  retries,
2799
3008
  useJSONContent,
2800
- disableTelemetry
3009
+ disableTelemetry,
3010
+ flowControl
2801
3011
  } = processOptions(options);
2802
3012
  telemetry = disableTelemetry ? void 0 : telemetry;
2803
3013
  const debug = WorkflowLogger.getLogger(verbose);
@@ -2838,6 +3048,7 @@ var serveBase = (routeFunction, telemetry, options) => {
2838
3048
  failureFunction,
2839
3049
  env,
2840
3050
  retries,
3051
+ flowControl,
2841
3052
  debug
2842
3053
  );
2843
3054
  if (failureCheck.isErr()) {
@@ -2846,6 +3057,7 @@ var serveBase = (routeFunction, telemetry, options) => {
2846
3057
  await debug?.log("WARN", "RESPONSE_DEFAULT", "failureFunction executed");
2847
3058
  return onStepFinish(workflowRunId, "failure-callback");
2848
3059
  }
3060
+ const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
2849
3061
  const workflowContext = new WorkflowContext({
2850
3062
  qstashClient,
2851
3063
  workflowRunId,
@@ -2857,7 +3069,9 @@ var serveBase = (routeFunction, telemetry, options) => {
2857
3069
  debug,
2858
3070
  env,
2859
3071
  retries,
2860
- telemetry
3072
+ telemetry,
3073
+ invokeCount,
3074
+ flowControl
2861
3075
  });
2862
3076
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
2863
3077
  routeFunction,
@@ -2880,6 +3094,7 @@ var serveBase = (routeFunction, telemetry, options) => {
2880
3094
  workflowUrl,
2881
3095
  failureUrl: workflowFailureUrl,
2882
3096
  retries,
3097
+ flowControl,
2883
3098
  telemetry,
2884
3099
  debug
2885
3100
  });
@@ -2889,10 +3104,16 @@ var serveBase = (routeFunction, telemetry, options) => {
2889
3104
  });
2890
3105
  throw callReturnCheck.error;
2891
3106
  } else if (callReturnCheck.value === "continue-workflow") {
2892
- const result = isFirstInvocation ? await triggerFirstInvocation({ workflowContext, useJSONContent, telemetry, debug }) : await triggerRouteFunction({
3107
+ const result = isFirstInvocation ? await triggerFirstInvocation({
3108
+ workflowContext,
3109
+ useJSONContent,
3110
+ telemetry,
3111
+ debug,
3112
+ invokeCount
3113
+ }) : await triggerRouteFunction({
2893
3114
  onStep: async () => routeFunction(workflowContext),
2894
- onCleanup: async () => {
2895
- await triggerWorkflowDelete(workflowContext, debug);
3115
+ onCleanup: async (result2) => {
3116
+ await triggerWorkflowDelete(workflowContext, result2, debug);
2896
3117
  },
2897
3118
  onCancel: async () => {
2898
3119
  await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
@@ -3095,7 +3316,8 @@ var Client4 = class {
3095
3316
  body,
3096
3317
  headers,
3097
3318
  workflowRunId,
3098
- retries
3319
+ retries,
3320
+ flowControl
3099
3321
  }) {
3100
3322
  const finalWorkflowRunId = getWorkflowRunId(workflowRunId);
3101
3323
  const context = new WorkflowContext({
@@ -3107,8 +3329,9 @@ var Client4 = class {
3107
3329
  url,
3108
3330
  workflowRunId: finalWorkflowRunId,
3109
3331
  retries,
3110
- telemetry: void 0
3332
+ telemetry: void 0,
3111
3333
  // can't know workflow telemetry here
3334
+ flowControl
3112
3335
  });
3113
3336
  const result = await triggerFirstInvocation({
3114
3337
  workflowContext: context,
@@ -3122,35 +3345,35 @@ var Client4 = class {
3122
3345
  }
3123
3346
  }
3124
3347
  /**
3125
- * Fetches logs for workflow runs.
3126
- *
3127
- * @param workflowRunId - The ID of the workflow run to fetch logs for.
3128
- * @param cursor - The cursor for pagination.
3129
- * @param count - Number of runs to fetch. Default value is 10.
3130
- * @param state - The state of the workflow run.
3131
- * @param workflowUrl - The URL of the workflow. Should be an exact match.
3132
- * @param workflowCreatedAt - The creation time of the workflow. If you have two workflow runs with the same URL, you can use this to filter them.
3133
- * @returns A promise that resolves to either a `WorkflowRunLog` or a `WorkflowRunResponse`.
3134
- *
3135
- * @example
3136
- * Fetch logs for a specific workflow run:
3137
- * ```typescript
3138
- * const { runs } = await client.logs({ workflowRunId: '12345' });
3139
- * const steps = runs[0].steps; // access steps
3140
- * ```
3141
- *
3142
- * @example
3143
- * Fetch logs with pagination:
3144
- * ```typescript
3145
- * const { runs, cursor } = await client.logs();
3146
- * const steps = runs[0].steps // access steps
3147
- *
3148
- * const { runs: nextRuns, cursor: nextCursor } = await client.logs({ cursor, count: 2 });
3149
- * ```
3150
- */
3348
+ * Fetches logs for workflow runs.
3349
+ *
3350
+ * @param workflowRunId - The ID of the workflow run to fetch logs for.
3351
+ * @param cursor - The cursor for pagination.
3352
+ * @param count - Number of runs to fetch. Default value is 10.
3353
+ * @param state - The state of the workflow run.
3354
+ * @param workflowUrl - The URL of the workflow. Should be an exact match.
3355
+ * @param workflowCreatedAt - The creation time of the workflow. If you have two workflow runs with the same URL, you can use this to filter them.
3356
+ * @returns A promise that resolves to either a `WorkflowRunLog` or a `WorkflowRunResponse`.
3357
+ *
3358
+ * @example
3359
+ * Fetch logs for a specific workflow run:
3360
+ * ```typescript
3361
+ * const { runs } = await client.logs({ workflowRunId: '12345' });
3362
+ * const steps = runs[0].steps; // access steps
3363
+ * ```
3364
+ *
3365
+ * @example
3366
+ * Fetch logs with pagination:
3367
+ * ```typescript
3368
+ * const { runs, cursor } = await client.logs();
3369
+ * const steps = runs[0].steps // access steps
3370
+ *
3371
+ * const { runs: nextRuns, cursor: nextCursor } = await client.logs({ cursor, count: 2 });
3372
+ * ```
3373
+ */
3151
3374
  async logs(params) {
3152
3375
  const { workflowRunId, cursor, count, state, workflowUrl, workflowCreatedAt } = params ?? {};
3153
- const urlParams = new URLSearchParams({ "groupBy": "workflowRunId" });
3376
+ const urlParams = new URLSearchParams({ groupBy: "workflowRunId" });
3154
3377
  if (workflowRunId) {
3155
3378
  urlParams.append("workflowRunId", workflowRunId);
3156
3379
  }