@upstash/workflow 1.3.3 → 1.4.0-rc
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/astro.d.mts +3 -2
- package/astro.d.ts +3 -2
- package/astro.js +587 -139
- package/astro.mjs +3 -1
- package/{chunk-WEQGCISK.mjs → chunk-UH6O5IJY.mjs} +749 -301
- package/cloudflare.d.mts +3 -2
- package/cloudflare.d.ts +3 -2
- package/cloudflare.js +587 -139
- package/cloudflare.mjs +3 -1
- package/express.d.mts +3 -2
- package/express.d.ts +3 -2
- package/express.js +588 -139
- package/express.mjs +4 -1
- package/h3.d.mts +3 -2
- package/h3.d.ts +3 -2
- package/h3.js +587 -139
- package/h3.mjs +3 -1
- package/hono.d.mts +3 -2
- package/hono.d.ts +3 -2
- package/hono.js +587 -139
- package/hono.mjs +3 -1
- package/index.d.mts +25 -6
- package/index.d.ts +25 -6
- package/index.js +610 -141
- package/index.mjs +26 -3
- package/nextjs.d.mts +3 -2
- package/nextjs.d.ts +3 -2
- package/nextjs.js +588 -139
- package/nextjs.mjs +4 -1
- package/package.json +1 -1
- package/react-router.d.mts +3 -2
- package/react-router.d.ts +3 -2
- package/react-router.js +587 -139
- package/react-router.mjs +3 -1
- package/{serve-many-iJF1IUXk.d.ts → serve-many-BCtZg31b.d.ts} +1 -1
- package/{serve-many-CG3BFvO3.d.mts → serve-many-CbIV7145.d.mts} +1 -1
- package/solidjs.d.mts +2 -1
- package/solidjs.d.ts +2 -1
- package/solidjs.js +587 -139
- package/solidjs.mjs +3 -1
- package/svelte.d.mts +3 -2
- package/svelte.d.ts +3 -2
- package/svelte.js +587 -139
- package/svelte.mjs +3 -1
- package/tanstack.d.mts +3 -2
- package/tanstack.d.ts +3 -2
- package/tanstack.js +587 -139
- package/tanstack.mjs +3 -1
- package/{types-CekOpKvz.d.ts → types-ffLPVG5_.d.mts} +248 -8
- package/{types-CekOpKvz.d.mts → types-ffLPVG5_.d.ts} +248 -8
package/express.js
CHANGED
|
@@ -34,6 +34,14 @@ var WORKFLOW_CREATED_AT_HEADER = "Upstash-Workflow-CreatedAt";
|
|
|
34
34
|
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
|
|
35
35
|
var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
|
|
36
36
|
var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
|
|
37
|
+
var WORKFLOW_FEATURE_SET = "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig";
|
|
38
|
+
var WORKFLOW_STEP_CONFIG_CALL_TYPE = "stepConfig";
|
|
39
|
+
var WORKFLOW_STEP_CONFIG_HEADER = "Upstash-Workflow-Step-Config";
|
|
40
|
+
var FLOW_CONTROL_KEY_HEADER = "Upstash-Flow-Control-Key";
|
|
41
|
+
var FLOW_CONTROL_VALUE_HEADER = "Upstash-Flow-Control-Value";
|
|
42
|
+
var RETRIES_HEADER = "Upstash-Retries";
|
|
43
|
+
var RETRY_DELAY_HEADER = "Upstash-Retry-Delay";
|
|
44
|
+
var WORKFLOW_STEP_CONFIG_FEATURE = "WF_StepConfig";
|
|
37
45
|
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
|
|
38
46
|
var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
|
|
39
47
|
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
|
|
@@ -51,6 +59,9 @@ var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
|
|
|
51
59
|
var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
|
|
52
60
|
var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
|
|
53
61
|
|
|
62
|
+
// src/serve/index.ts
|
|
63
|
+
var import_qstash12 = require("@upstash/qstash");
|
|
64
|
+
|
|
54
65
|
// src/client/utils.ts
|
|
55
66
|
var import_qstash2 = require("@upstash/qstash");
|
|
56
67
|
|
|
@@ -231,102 +242,6 @@ var getSteps = async (requester, workflowRunId, messageId, dispatchDebug) => {
|
|
|
231
242
|
}
|
|
232
243
|
};
|
|
233
244
|
|
|
234
|
-
// src/context/auto-executor.ts
|
|
235
|
-
var import_qstash5 = require("@upstash/qstash");
|
|
236
|
-
|
|
237
|
-
// src/qstash/headers.ts
|
|
238
|
-
var import_qstash4 = require("@upstash/qstash");
|
|
239
|
-
|
|
240
|
-
// src/utils.ts
|
|
241
|
-
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
242
|
-
var NANOID_LENGTH = 21;
|
|
243
|
-
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
244
|
-
function validateLabel(label) {
|
|
245
|
-
if (label === void 0) return;
|
|
246
|
-
const labels = Array.isArray(label) ? label : [label];
|
|
247
|
-
if (labels.length === 0) {
|
|
248
|
-
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
249
|
-
}
|
|
250
|
-
for (const value of labels) {
|
|
251
|
-
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
252
|
-
throw new WorkflowNonRetryableError(
|
|
253
|
-
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
function serializeLabel(label) {
|
|
259
|
-
return Array.isArray(label) ? label.join(",") : label;
|
|
260
|
-
}
|
|
261
|
-
function validateFlowControl(flowControl) {
|
|
262
|
-
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
263
|
-
throw new WorkflowNonRetryableError(
|
|
264
|
-
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
function getRandomInt() {
|
|
269
|
-
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
270
|
-
}
|
|
271
|
-
function nanoid(length = NANOID_LENGTH) {
|
|
272
|
-
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
273
|
-
}
|
|
274
|
-
function getWorkflowRunId(id) {
|
|
275
|
-
return `wfr_${id ?? nanoid()}`;
|
|
276
|
-
}
|
|
277
|
-
function decodeBase64(base64) {
|
|
278
|
-
const binString = atob(base64);
|
|
279
|
-
try {
|
|
280
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
281
|
-
return new TextDecoder().decode(intArray);
|
|
282
|
-
} catch (error) {
|
|
283
|
-
console.warn(
|
|
284
|
-
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
285
|
-
);
|
|
286
|
-
return binString;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
function getUserIdFromToken(qstashClient) {
|
|
290
|
-
try {
|
|
291
|
-
const token = qstashClient.token;
|
|
292
|
-
const decodedToken = decodeBase64(token);
|
|
293
|
-
const tokenPayload = JSON.parse(decodedToken);
|
|
294
|
-
const userId = tokenPayload.UserID;
|
|
295
|
-
if (!userId) {
|
|
296
|
-
throw new WorkflowError("QStash token payload does not contain userId");
|
|
297
|
-
}
|
|
298
|
-
return userId;
|
|
299
|
-
} catch (error) {
|
|
300
|
-
throw new WorkflowError(
|
|
301
|
-
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
function getQStashUrl(qstashClient) {
|
|
306
|
-
try {
|
|
307
|
-
const requester = qstashClient.http;
|
|
308
|
-
const baseUrl = requester.baseUrl;
|
|
309
|
-
if (!baseUrl) {
|
|
310
|
-
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
311
|
-
}
|
|
312
|
-
return baseUrl;
|
|
313
|
-
} catch (error) {
|
|
314
|
-
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
function getEventId() {
|
|
318
|
-
return `evt_${nanoid(15)}`;
|
|
319
|
-
}
|
|
320
|
-
function stringifyBody(body) {
|
|
321
|
-
if (body === void 0) {
|
|
322
|
-
return void 0;
|
|
323
|
-
}
|
|
324
|
-
if (typeof body === "string") {
|
|
325
|
-
return body;
|
|
326
|
-
}
|
|
327
|
-
return JSON.stringify(body);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
245
|
// node_modules/neverthrow/dist/index.es.js
|
|
331
246
|
var defaultErrorConfig = {
|
|
332
247
|
withStackTrace: false
|
|
@@ -742,6 +657,102 @@ var Err = class {
|
|
|
742
657
|
};
|
|
743
658
|
var fromThrowable = Result.fromThrowable;
|
|
744
659
|
|
|
660
|
+
// src/context/auto-executor.ts
|
|
661
|
+
var import_qstash5 = require("@upstash/qstash");
|
|
662
|
+
|
|
663
|
+
// src/qstash/headers.ts
|
|
664
|
+
var import_qstash4 = require("@upstash/qstash");
|
|
665
|
+
|
|
666
|
+
// src/utils.ts
|
|
667
|
+
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
668
|
+
var NANOID_LENGTH = 21;
|
|
669
|
+
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
670
|
+
function validateLabel(label) {
|
|
671
|
+
if (label === void 0) return;
|
|
672
|
+
const labels = Array.isArray(label) ? label : [label];
|
|
673
|
+
if (labels.length === 0) {
|
|
674
|
+
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
675
|
+
}
|
|
676
|
+
for (const value of labels) {
|
|
677
|
+
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
678
|
+
throw new WorkflowNonRetryableError(
|
|
679
|
+
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function serializeLabel(label) {
|
|
685
|
+
return Array.isArray(label) ? label.join(",") : label;
|
|
686
|
+
}
|
|
687
|
+
function validateFlowControl(flowControl) {
|
|
688
|
+
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
689
|
+
throw new WorkflowNonRetryableError(
|
|
690
|
+
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function getRandomInt() {
|
|
695
|
+
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
696
|
+
}
|
|
697
|
+
function nanoid(length = NANOID_LENGTH) {
|
|
698
|
+
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
699
|
+
}
|
|
700
|
+
function getWorkflowRunId(id) {
|
|
701
|
+
return `wfr_${id ?? nanoid()}`;
|
|
702
|
+
}
|
|
703
|
+
function decodeBase64(base64) {
|
|
704
|
+
const binString = atob(base64);
|
|
705
|
+
try {
|
|
706
|
+
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
707
|
+
return new TextDecoder().decode(intArray);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
console.warn(
|
|
710
|
+
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
711
|
+
);
|
|
712
|
+
return binString;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function getUserIdFromToken(qstashClient) {
|
|
716
|
+
try {
|
|
717
|
+
const token = qstashClient.token;
|
|
718
|
+
const decodedToken = decodeBase64(token);
|
|
719
|
+
const tokenPayload = JSON.parse(decodedToken);
|
|
720
|
+
const userId = tokenPayload.UserID;
|
|
721
|
+
if (!userId) {
|
|
722
|
+
throw new WorkflowError("QStash token payload does not contain userId");
|
|
723
|
+
}
|
|
724
|
+
return userId;
|
|
725
|
+
} catch (error) {
|
|
726
|
+
throw new WorkflowError(
|
|
727
|
+
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function getQStashUrl(qstashClient) {
|
|
732
|
+
try {
|
|
733
|
+
const requester = qstashClient.http;
|
|
734
|
+
const baseUrl = requester.baseUrl;
|
|
735
|
+
if (!baseUrl) {
|
|
736
|
+
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
737
|
+
}
|
|
738
|
+
return baseUrl;
|
|
739
|
+
} catch (error) {
|
|
740
|
+
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function getEventId() {
|
|
744
|
+
return `evt_${nanoid(15)}`;
|
|
745
|
+
}
|
|
746
|
+
function stringifyBody(body) {
|
|
747
|
+
if (body === void 0) {
|
|
748
|
+
return void 0;
|
|
749
|
+
}
|
|
750
|
+
if (typeof body === "string") {
|
|
751
|
+
return body;
|
|
752
|
+
}
|
|
753
|
+
return JSON.stringify(body);
|
|
754
|
+
}
|
|
755
|
+
|
|
745
756
|
// src/types.ts
|
|
746
757
|
var StepTypes = [
|
|
747
758
|
"Initial",
|
|
@@ -847,6 +858,10 @@ var triggerFirstInvocation = async (params) => {
|
|
|
847
858
|
return err(error_);
|
|
848
859
|
}
|
|
849
860
|
};
|
|
861
|
+
var flushPendingStep = async (workflowContext) => {
|
|
862
|
+
const { executor } = workflowContext;
|
|
863
|
+
return await executor.submitPendingStep();
|
|
864
|
+
};
|
|
850
865
|
var triggerRouteFunction = async ({
|
|
851
866
|
onCleanup,
|
|
852
867
|
onStep,
|
|
@@ -1054,8 +1069,29 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
|
|
|
1054
1069
|
var BaseLazyStep = class _BaseLazyStep {
|
|
1055
1070
|
stepName;
|
|
1056
1071
|
context;
|
|
1057
|
-
|
|
1072
|
+
/**
|
|
1073
|
+
* step-level settings (flow control, retries etc.) which override the
|
|
1074
|
+
* settings the workflow run was triggered with, for this step only.
|
|
1075
|
+
*/
|
|
1076
|
+
stepSettings;
|
|
1077
|
+
/**
|
|
1078
|
+
* whether this step's result can be submitted after the route function
|
|
1079
|
+
* has moved on, instead of right away.
|
|
1080
|
+
*
|
|
1081
|
+
* Enabled for steps whose `getResultStep` produces the final `out` —
|
|
1082
|
+
* which is not the same as "needs no server round trip":
|
|
1083
|
+
* `LazyCreateWebhookStep` builds its result in `getBody` and returns
|
|
1084
|
+
* `out: undefined` from `getResultStep`, so deferring it would hand
|
|
1085
|
+
* `undefined` to the route function.
|
|
1086
|
+
*
|
|
1087
|
+
* Deferring lets the route function continue and reveal the next step,
|
|
1088
|
+
* so that step's settings can ride on this step's submission instead
|
|
1089
|
+
* of needing a step config request of their own.
|
|
1090
|
+
*/
|
|
1091
|
+
supportsDeferredSubmission = false;
|
|
1092
|
+
constructor(context, stepName, stepSettings) {
|
|
1058
1093
|
this.context = context;
|
|
1094
|
+
this.stepSettings = stepSettings;
|
|
1059
1095
|
if (!stepName) {
|
|
1060
1096
|
throw new WorkflowError(
|
|
1061
1097
|
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
|
|
@@ -1119,7 +1155,13 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1119
1155
|
step.out = JSON.stringify(step.out);
|
|
1120
1156
|
return JSON.stringify(step);
|
|
1121
1157
|
}
|
|
1122
|
-
getHeaders({
|
|
1158
|
+
getHeaders({
|
|
1159
|
+
context,
|
|
1160
|
+
telemetry: telemetry2,
|
|
1161
|
+
invokeCount,
|
|
1162
|
+
step,
|
|
1163
|
+
stepSettings
|
|
1164
|
+
}) {
|
|
1123
1165
|
return getHeaders({
|
|
1124
1166
|
initHeaderValue: "false",
|
|
1125
1167
|
workflowConfig: {
|
|
@@ -1132,7 +1174,8 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1132
1174
|
stepInfo: {
|
|
1133
1175
|
step,
|
|
1134
1176
|
lazyStep: this
|
|
1135
|
-
}
|
|
1177
|
+
},
|
|
1178
|
+
stepSettings
|
|
1136
1179
|
});
|
|
1137
1180
|
}
|
|
1138
1181
|
async submitStep({ context, body, headers }) {
|
|
@@ -1150,8 +1193,9 @@ var LazyFunctionStep = class extends BaseLazyStep {
|
|
|
1150
1193
|
stepFunction;
|
|
1151
1194
|
stepType = "Run";
|
|
1152
1195
|
allowUndefinedOut = true;
|
|
1153
|
-
|
|
1154
|
-
|
|
1196
|
+
supportsDeferredSubmission = true;
|
|
1197
|
+
constructor(context, stepName, stepFunction, stepSettings) {
|
|
1198
|
+
super(context, stepName, stepSettings);
|
|
1155
1199
|
this.stepFunction = stepFunction;
|
|
1156
1200
|
}
|
|
1157
1201
|
getPlanStep(concurrent, targetStep) {
|
|
@@ -1181,6 +1225,7 @@ var LazySleepStep = class extends BaseLazyStep {
|
|
|
1181
1225
|
sleep;
|
|
1182
1226
|
stepType = "SleepFor";
|
|
1183
1227
|
allowUndefinedOut = true;
|
|
1228
|
+
supportsDeferredSubmission = true;
|
|
1184
1229
|
constructor(context, stepName, sleep) {
|
|
1185
1230
|
super(context, stepName);
|
|
1186
1231
|
this.sleep = sleep;
|
|
@@ -1220,6 +1265,7 @@ var LazySleepUntilStep = class extends BaseLazyStep {
|
|
|
1220
1265
|
sleepUntil;
|
|
1221
1266
|
stepType = "SleepUntil";
|
|
1222
1267
|
allowUndefinedOut = true;
|
|
1268
|
+
supportsDeferredSubmission = true;
|
|
1223
1269
|
constructor(context, stepName, sleepUntil) {
|
|
1224
1270
|
super(context, stepName);
|
|
1225
1271
|
this.sleepUntil = sleepUntil;
|
|
@@ -1712,6 +1758,7 @@ var WorkflowHeaders = class {
|
|
|
1712
1758
|
invokeCount;
|
|
1713
1759
|
initHeaderValue;
|
|
1714
1760
|
stepInfo;
|
|
1761
|
+
stepSettings;
|
|
1715
1762
|
headers;
|
|
1716
1763
|
/**
|
|
1717
1764
|
* @param params workflow header parameters
|
|
@@ -1721,17 +1768,20 @@ var WorkflowHeaders = class {
|
|
|
1721
1768
|
workflowConfig,
|
|
1722
1769
|
invokeCount,
|
|
1723
1770
|
initHeaderValue,
|
|
1724
|
-
stepInfo
|
|
1771
|
+
stepInfo,
|
|
1772
|
+
stepSettings
|
|
1725
1773
|
}) {
|
|
1726
1774
|
this.userHeaders = userHeaders;
|
|
1727
1775
|
this.workflowConfig = workflowConfig;
|
|
1728
1776
|
this.invokeCount = invokeCount;
|
|
1729
1777
|
this.initHeaderValue = initHeaderValue;
|
|
1730
1778
|
this.stepInfo = stepInfo;
|
|
1779
|
+
this.stepSettings = stepSettings;
|
|
1731
1780
|
this.headers = {
|
|
1732
1781
|
rawHeaders: {},
|
|
1733
1782
|
workflowHeaders: {},
|
|
1734
|
-
failureHeaders: {}
|
|
1783
|
+
failureHeaders: {},
|
|
1784
|
+
stepSettingsHeaders: {}
|
|
1735
1785
|
};
|
|
1736
1786
|
}
|
|
1737
1787
|
getHeaders() {
|
|
@@ -1742,6 +1792,7 @@ var WorkflowHeaders = class {
|
|
|
1742
1792
|
this.addUserHeaders();
|
|
1743
1793
|
this.addInvokeCount();
|
|
1744
1794
|
this.addFailureUrl();
|
|
1795
|
+
this.addStepSettings();
|
|
1745
1796
|
const contentType = this.addContentType();
|
|
1746
1797
|
return this.prefixHeaders(contentType);
|
|
1747
1798
|
}
|
|
@@ -1751,7 +1802,7 @@ var WorkflowHeaders = class {
|
|
|
1751
1802
|
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
|
|
1752
1803
|
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
|
|
1753
1804
|
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
|
|
1754
|
-
[WORKFLOW_FEATURE_HEADER]:
|
|
1805
|
+
[WORKFLOW_FEATURE_HEADER]: WORKFLOW_FEATURE_SET,
|
|
1755
1806
|
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
1756
1807
|
...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
|
|
1757
1808
|
};
|
|
@@ -1829,7 +1880,7 @@ var WorkflowHeaders = class {
|
|
|
1829
1880
|
this.headers.failureHeaders["Workflow-Init"] = "false";
|
|
1830
1881
|
this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
|
|
1831
1882
|
this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
|
|
1832
|
-
this.headers.failureHeaders["Feature-Set"] =
|
|
1883
|
+
this.headers.failureHeaders["Feature-Set"] = WORKFLOW_FEATURE_SET;
|
|
1833
1884
|
if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
|
|
1834
1885
|
this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
|
|
1835
1886
|
}
|
|
@@ -1837,6 +1888,42 @@ var WorkflowHeaders = class {
|
|
|
1837
1888
|
this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
|
|
1838
1889
|
}
|
|
1839
1890
|
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Applies the step-level settings of a step to the message.
|
|
1893
|
+
*
|
|
1894
|
+
* These headers configure the message itself, so that QStash uses them
|
|
1895
|
+
* instead of the settings the run was triggered with. The delivery of
|
|
1896
|
+
* the message is what executes the step, which is how a step's
|
|
1897
|
+
* settings reach it.
|
|
1898
|
+
*
|
|
1899
|
+
* When any setting is present the feature set is extended with
|
|
1900
|
+
* `WF_StepConfig`, which is what tells QStash to keep them. QStash
|
|
1901
|
+
* reports that back on the delivery as `Upstash-Workflow-Step-Config`.
|
|
1902
|
+
*/
|
|
1903
|
+
addStepSettings() {
|
|
1904
|
+
if (!this.stepSettings) {
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
const headers = {};
|
|
1908
|
+
if (this.stepSettings.flowControl) {
|
|
1909
|
+
const { flowControlKey, flowControlValue } = prepareFlowControl(
|
|
1910
|
+
this.stepSettings.flowControl
|
|
1911
|
+
);
|
|
1912
|
+
headers[FLOW_CONTROL_KEY_HEADER] = flowControlKey;
|
|
1913
|
+
headers[FLOW_CONTROL_VALUE_HEADER] = flowControlValue;
|
|
1914
|
+
}
|
|
1915
|
+
if (this.stepSettings.retries !== void 0) {
|
|
1916
|
+
headers[RETRIES_HEADER] = this.stepSettings.retries.toString();
|
|
1917
|
+
}
|
|
1918
|
+
if (this.stepSettings.retryDelay) {
|
|
1919
|
+
headers[RETRY_DELAY_HEADER] = this.stepSettings.retryDelay;
|
|
1920
|
+
}
|
|
1921
|
+
if (Object.keys(headers).length === 0) {
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
headers[WORKFLOW_FEATURE_HEADER] = `${WORKFLOW_FEATURE_SET},${WORKFLOW_STEP_CONFIG_FEATURE}`;
|
|
1925
|
+
this.headers.stepSettingsHeaders = headers;
|
|
1926
|
+
}
|
|
1840
1927
|
addContentType() {
|
|
1841
1928
|
if (this.workflowConfig.useJSONContent) {
|
|
1842
1929
|
this.headers.rawHeaders["content-type"] = "application/json";
|
|
@@ -1850,14 +1937,17 @@ var WorkflowHeaders = class {
|
|
|
1850
1937
|
return contentType;
|
|
1851
1938
|
}
|
|
1852
1939
|
prefixHeaders(contentType) {
|
|
1853
|
-
const { rawHeaders, workflowHeaders, failureHeaders } = this.headers;
|
|
1940
|
+
const { rawHeaders, workflowHeaders, failureHeaders, stepSettingsHeaders } = this.headers;
|
|
1854
1941
|
const isCall = this.stepInfo?.lazyStep.stepType === "Call";
|
|
1855
1942
|
return {
|
|
1856
1943
|
headers: {
|
|
1857
1944
|
...rawHeaders,
|
|
1858
1945
|
...addPrefixToHeaders(workflowHeaders, isCall ? "Upstash-Callback-" : "Upstash-"),
|
|
1859
1946
|
...addPrefixToHeaders(failureHeaders, "Upstash-Failure-Callback-"),
|
|
1860
|
-
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {}
|
|
1947
|
+
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {},
|
|
1948
|
+
// last, so they override the run's settings above. Never
|
|
1949
|
+
// prefixed: they configure this message, not its callback.
|
|
1950
|
+
...stepSettingsHeaders
|
|
1861
1951
|
},
|
|
1862
1952
|
contentType
|
|
1863
1953
|
};
|
|
@@ -1908,7 +1998,7 @@ var submitParallelSteps = async ({
|
|
|
1908
1998
|
info: `Submitting ${planSteps.length} parallel steps.`
|
|
1909
1999
|
});
|
|
1910
2000
|
const result = await context.qstashClient.batch(
|
|
1911
|
-
planSteps.map((planStep) => {
|
|
2001
|
+
planSteps.map((planStep, index) => {
|
|
1912
2002
|
const { headers } = getHeaders({
|
|
1913
2003
|
initHeaderValue: "false",
|
|
1914
2004
|
workflowConfig: {
|
|
@@ -1916,7 +2006,8 @@ var submitParallelSteps = async ({
|
|
|
1916
2006
|
workflowUrl: context.url,
|
|
1917
2007
|
telemetry: telemetry2
|
|
1918
2008
|
},
|
|
1919
|
-
invokeCount
|
|
2009
|
+
invokeCount,
|
|
2010
|
+
stepSettings: steps[index].stepSettings
|
|
1920
2011
|
});
|
|
1921
2012
|
return {
|
|
1922
2013
|
headers,
|
|
@@ -1935,31 +2026,38 @@ var submitParallelSteps = async ({
|
|
|
1935
2026
|
}
|
|
1936
2027
|
throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
|
|
1937
2028
|
};
|
|
1938
|
-
var
|
|
1939
|
-
context,
|
|
2029
|
+
var executeStep = async ({
|
|
1940
2030
|
lazyStep,
|
|
1941
2031
|
stepId,
|
|
1942
|
-
invokeCount,
|
|
1943
2032
|
concurrency,
|
|
1944
|
-
telemetry: telemetry2,
|
|
1945
|
-
dispatchDebug,
|
|
1946
2033
|
dispatchLifecycle
|
|
1947
2034
|
}) => {
|
|
1948
2035
|
await dispatchLifecycle("beforeExecution", {
|
|
1949
2036
|
stepName: lazyStep.stepName
|
|
1950
2037
|
});
|
|
1951
|
-
let resultStep;
|
|
1952
2038
|
try {
|
|
1953
|
-
|
|
2039
|
+
return await lazyStep.getResultStep(concurrency, stepId);
|
|
1954
2040
|
} catch (error) {
|
|
1955
2041
|
attachStepNameToError(error, lazyStep.stepName);
|
|
1956
2042
|
throw error;
|
|
1957
2043
|
}
|
|
2044
|
+
};
|
|
2045
|
+
var submitStepResult = async ({
|
|
2046
|
+
context,
|
|
2047
|
+
lazyStep,
|
|
2048
|
+
resultStep,
|
|
2049
|
+
invokeCount,
|
|
2050
|
+
concurrency,
|
|
2051
|
+
telemetry: telemetry2,
|
|
2052
|
+
dispatchDebug,
|
|
2053
|
+
nextStepSettings
|
|
2054
|
+
}) => {
|
|
1958
2055
|
const { headers } = lazyStep.getHeaders({
|
|
1959
2056
|
context,
|
|
1960
2057
|
step: resultStep,
|
|
1961
2058
|
invokeCount,
|
|
1962
|
-
telemetry: telemetry2
|
|
2059
|
+
telemetry: telemetry2,
|
|
2060
|
+
stepSettings: nextStepSettings
|
|
1963
2061
|
});
|
|
1964
2062
|
const body = lazyStep.getBody({
|
|
1965
2063
|
context,
|
|
@@ -1984,6 +2082,138 @@ var submitSingleStep = async ({
|
|
|
1984
2082
|
}
|
|
1985
2083
|
return resultStep;
|
|
1986
2084
|
};
|
|
2085
|
+
var publishStepConfigRequest = async ({
|
|
2086
|
+
context,
|
|
2087
|
+
lazyStep,
|
|
2088
|
+
targetStep,
|
|
2089
|
+
invokeCount,
|
|
2090
|
+
telemetry: telemetry2,
|
|
2091
|
+
dispatchDebug
|
|
2092
|
+
}) => {
|
|
2093
|
+
const { headers } = getHeaders({
|
|
2094
|
+
initHeaderValue: "false",
|
|
2095
|
+
workflowConfig: {
|
|
2096
|
+
workflowRunId: context.workflowRunId,
|
|
2097
|
+
workflowUrl: context.url,
|
|
2098
|
+
telemetry: telemetry2
|
|
2099
|
+
},
|
|
2100
|
+
invokeCount,
|
|
2101
|
+
stepSettings: lazyStep.stepSettings
|
|
2102
|
+
});
|
|
2103
|
+
await dispatchDebug("onInfo", {
|
|
2104
|
+
info: `Step "${lazyStep.stepName}" (${targetStep}) has step-level settings which the current delivery does not carry. Requesting a delivery which does.`
|
|
2105
|
+
});
|
|
2106
|
+
const result = await context.qstashClient.publishJSON({
|
|
2107
|
+
headers: {
|
|
2108
|
+
...headers,
|
|
2109
|
+
"Upstash-Workflow-CallType": WORKFLOW_STEP_CONFIG_CALL_TYPE
|
|
2110
|
+
},
|
|
2111
|
+
method: "POST",
|
|
2112
|
+
body: { targetStep, invokeCount },
|
|
2113
|
+
url: context.url,
|
|
2114
|
+
contentBasedDeduplication: true
|
|
2115
|
+
});
|
|
2116
|
+
if (result?.deduplicated) {
|
|
2117
|
+
await dispatchDebug("onWarning", {
|
|
2118
|
+
warning: `A step config request for step "${lazyStep.stepName}" (${targetStep}) was already published and this one was deduplicated. Expected when the delivery which published it is being retried; otherwise the run stops here.`
|
|
2119
|
+
});
|
|
2120
|
+
} else if (result?.messageId) {
|
|
2121
|
+
await dispatchDebug("onInfo", {
|
|
2122
|
+
info: `Requested a delivery under the settings of step "${lazyStep.stepName}" with messageId: ${result.messageId}.`
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
|
|
2127
|
+
// src/qstash/step-config.ts
|
|
2128
|
+
var DEFAULT_PARALLELISM = 0;
|
|
2129
|
+
var DEFAULT_RATE = 0;
|
|
2130
|
+
var DEFAULT_PERIOD_SECONDS = 1;
|
|
2131
|
+
var SECONDS_PER_UNIT = {
|
|
2132
|
+
s: 1,
|
|
2133
|
+
m: 60,
|
|
2134
|
+
h: 60 * 60,
|
|
2135
|
+
d: 24 * 60 * 60
|
|
2136
|
+
};
|
|
2137
|
+
var durationToSeconds = (duration) => {
|
|
2138
|
+
if (typeof duration === "number") {
|
|
2139
|
+
return Number.isFinite(duration) ? duration : void 0;
|
|
2140
|
+
}
|
|
2141
|
+
const match = /^(\d+)([smhd])$/.exec(duration.trim());
|
|
2142
|
+
if (match) {
|
|
2143
|
+
return Number(match[1]) * SECONDS_PER_UNIT[match[2]];
|
|
2144
|
+
}
|
|
2145
|
+
const seconds = Number(duration.trim());
|
|
2146
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2147
|
+
};
|
|
2148
|
+
var normalizeFlowControl = (flowControl) => {
|
|
2149
|
+
const period = flowControl.period === void 0 ? void 0 : durationToSeconds(flowControl.period);
|
|
2150
|
+
return {
|
|
2151
|
+
key: flowControl.key,
|
|
2152
|
+
parallelism: flowControl.parallelism ?? DEFAULT_PARALLELISM,
|
|
2153
|
+
rate: flowControl.rate ?? flowControl.ratePerSecond ?? DEFAULT_RATE,
|
|
2154
|
+
period: period ?? DEFAULT_PERIOD_SECONDS
|
|
2155
|
+
};
|
|
2156
|
+
};
|
|
2157
|
+
var parseFlowControlHeaders = (key, value) => {
|
|
2158
|
+
if (!key) {
|
|
2159
|
+
return void 0;
|
|
2160
|
+
}
|
|
2161
|
+
const flowControl = {
|
|
2162
|
+
key,
|
|
2163
|
+
parallelism: DEFAULT_PARALLELISM,
|
|
2164
|
+
rate: DEFAULT_RATE,
|
|
2165
|
+
period: DEFAULT_PERIOD_SECONDS
|
|
2166
|
+
};
|
|
2167
|
+
for (const entry of (value ?? "").split(",")) {
|
|
2168
|
+
const [name, rawValue] = entry.split("=").map((part) => part.trim());
|
|
2169
|
+
if (!name || rawValue === void 0) {
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
switch (name) {
|
|
2173
|
+
case "parallelism": {
|
|
2174
|
+
flowControl.parallelism = Number(rawValue);
|
|
2175
|
+
break;
|
|
2176
|
+
}
|
|
2177
|
+
case "rate": {
|
|
2178
|
+
flowControl.rate = Number(rawValue);
|
|
2179
|
+
break;
|
|
2180
|
+
}
|
|
2181
|
+
case "period": {
|
|
2182
|
+
flowControl.period = durationToSeconds(rawValue) ?? DEFAULT_PERIOD_SECONDS;
|
|
2183
|
+
break;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return flowControl;
|
|
2188
|
+
};
|
|
2189
|
+
var getEffectiveConfig = (headers) => {
|
|
2190
|
+
const retriesHeader = headers.get(RETRIES_HEADER);
|
|
2191
|
+
return {
|
|
2192
|
+
flowControl: parseFlowControlHeaders(
|
|
2193
|
+
headers.get(FLOW_CONTROL_KEY_HEADER),
|
|
2194
|
+
headers.get(FLOW_CONTROL_VALUE_HEADER)
|
|
2195
|
+
),
|
|
2196
|
+
retries: retriesHeader === null ? void 0 : Number(retriesHeader),
|
|
2197
|
+
retryDelay: headers.get(RETRY_DELAY_HEADER) ?? void 0,
|
|
2198
|
+
hasStepConfig: headers.get(WORKFLOW_STEP_CONFIG_HEADER) === "true"
|
|
2199
|
+
};
|
|
2200
|
+
};
|
|
2201
|
+
var describeStepSettingsMismatch = (stepSettings, effectiveConfig) => {
|
|
2202
|
+
if (stepSettings.flowControl) {
|
|
2203
|
+
const wanted = normalizeFlowControl(stepSettings.flowControl);
|
|
2204
|
+
const applied = effectiveConfig.flowControl;
|
|
2205
|
+
if (!applied || applied.key !== wanted.key || applied.parallelism !== wanted.parallelism || applied.rate !== wanted.rate || applied.period !== wanted.period) {
|
|
2206
|
+
return `flow control: expected ${JSON.stringify(wanted)}, delivery has ${JSON.stringify(applied)}`;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
if (stepSettings.retries !== void 0 && effectiveConfig.retries !== void 0 && stepSettings.retries !== effectiveConfig.retries) {
|
|
2210
|
+
return `retries: expected ${stepSettings.retries}, delivery has ${effectiveConfig.retries}`;
|
|
2211
|
+
}
|
|
2212
|
+
if (stepSettings.retryDelay !== void 0 && stepSettings.retryDelay !== effectiveConfig.retryDelay) {
|
|
2213
|
+
return `retry delay: expected '${stepSettings.retryDelay}', delivery has '${effectiveConfig.retryDelay ?? ""}'`;
|
|
2214
|
+
}
|
|
2215
|
+
return void 0;
|
|
2216
|
+
};
|
|
1987
2217
|
|
|
1988
2218
|
// src/context/auto-executor.ts
|
|
1989
2219
|
var AutoExecutor = class _AutoExecutor {
|
|
@@ -2000,6 +2230,25 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2000
2230
|
stepCount = 0;
|
|
2001
2231
|
planStepCount = 0;
|
|
2002
2232
|
executingStep = false;
|
|
2233
|
+
/**
|
|
2234
|
+
* an executed step whose result hasn't been submitted to QStash yet.
|
|
2235
|
+
*
|
|
2236
|
+
* Set when a step which can produce its result in-process executes. The
|
|
2237
|
+
* submission waits so that the route function can continue and reveal
|
|
2238
|
+
* what comes next: if that is a single step with step-level settings,
|
|
2239
|
+
* the settings ride on this submission and no step config request is
|
|
2240
|
+
* needed, since the delivery of the submission is what executes it.
|
|
2241
|
+
*
|
|
2242
|
+
* Flushed when the next step is reached (see `addStep`) or when the
|
|
2243
|
+
* route function ends (see `flushPendingStep` in `serve`).
|
|
2244
|
+
*/
|
|
2245
|
+
pendingStep;
|
|
2246
|
+
/**
|
|
2247
|
+
* configuration QStash applied to the delivery being handled. A step
|
|
2248
|
+
* with step-level settings is settled by comparing its settings against
|
|
2249
|
+
* this.
|
|
2250
|
+
*/
|
|
2251
|
+
effectiveConfig;
|
|
2003
2252
|
/**
|
|
2004
2253
|
* @param context workflow context
|
|
2005
2254
|
* @param steps list of steps
|
|
@@ -2007,14 +2256,17 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2007
2256
|
* @param dispatchLifecycle lifecycle event dispatcher
|
|
2008
2257
|
* @param telemetry optional telemetry information
|
|
2009
2258
|
* @param invokeCount optional invoke count
|
|
2259
|
+
* @param effectiveConfig configuration QStash applied to the delivery
|
|
2260
|
+
* being handled
|
|
2010
2261
|
*/
|
|
2011
|
-
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry2, invokeCount) {
|
|
2262
|
+
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry2, invokeCount, effectiveConfig) {
|
|
2012
2263
|
this.context = context;
|
|
2013
2264
|
this.steps = steps;
|
|
2014
2265
|
this.dispatchDebug = dispatchDebug;
|
|
2015
2266
|
this.dispatchLifecycle = dispatchLifecycle;
|
|
2016
2267
|
this.telemetry = telemetry2;
|
|
2017
2268
|
this.invokeCount = invokeCount ?? 0;
|
|
2269
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2018
2270
|
this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
|
|
2019
2271
|
}
|
|
2020
2272
|
/**
|
|
@@ -2049,6 +2301,15 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2049
2301
|
lazyStepList.push(stepInfo);
|
|
2050
2302
|
const index = this.indexInCurrentList++;
|
|
2051
2303
|
const requestComplete = this.deferExecution().then(async () => {
|
|
2304
|
+
const submitted = await this.submitPendingStep(
|
|
2305
|
+
lazyStepList.length === 1 ? lazyStepList[0].stepSettings : void 0
|
|
2306
|
+
);
|
|
2307
|
+
if (submitted.isErr()) {
|
|
2308
|
+
throw submitted.error;
|
|
2309
|
+
}
|
|
2310
|
+
if (submitted.value.result === "submitted-step") {
|
|
2311
|
+
throw submitted.value.abort;
|
|
2312
|
+
}
|
|
2052
2313
|
if (!this.promises.has(lazyStepList)) {
|
|
2053
2314
|
const promise2 = this.getExecutionPromise(lazyStepList);
|
|
2054
2315
|
this.promises.set(lazyStepList, promise2);
|
|
@@ -2102,18 +2363,100 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2102
2363
|
}
|
|
2103
2364
|
return parsedOut;
|
|
2104
2365
|
}
|
|
2105
|
-
const
|
|
2106
|
-
|
|
2366
|
+
const mismatch = lazyStep.stepSettings ? describeStepSettingsMismatch(lazyStep.stepSettings, this.effectiveConfig) : void 0;
|
|
2367
|
+
if (mismatch) {
|
|
2368
|
+
if (!this.effectiveConfig.hasStepConfig) {
|
|
2369
|
+
await publishStepConfigRequest({
|
|
2370
|
+
context: this.context,
|
|
2371
|
+
lazyStep,
|
|
2372
|
+
targetStep: this.stepCount,
|
|
2373
|
+
invokeCount: this.invokeCount,
|
|
2374
|
+
telemetry: this.telemetry,
|
|
2375
|
+
dispatchDebug: this.dispatchDebug
|
|
2376
|
+
});
|
|
2377
|
+
throw new WorkflowAbort(lazyStep.stepName);
|
|
2378
|
+
} else {
|
|
2379
|
+
await this.dispatchDebug("onWarning", {
|
|
2380
|
+
warning: `The step-level settings of step '${lazyStep.stepName}' were published but are not recognized on the delivery which carries them (${mismatch}). Executing the step with the settings of the delivery instead. This is a bug in @upstash/workflow, please report it.`
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
const resultStep = await executeStep({
|
|
2107
2385
|
lazyStep,
|
|
2108
2386
|
stepId: this.stepCount,
|
|
2387
|
+
concurrency: NO_CONCURRENCY,
|
|
2388
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2389
|
+
});
|
|
2390
|
+
if (lazyStep.supportsDeferredSubmission) {
|
|
2391
|
+
this.pendingStep = { status: "held", lazyStep, resultStep };
|
|
2392
|
+
return lazyStep.parseOut({
|
|
2393
|
+
...resultStep,
|
|
2394
|
+
out: resultStep.out === void 0 ? void 0 : JSON.stringify(resultStep.out)
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
await submitStepResult({
|
|
2398
|
+
context: this.context,
|
|
2399
|
+
lazyStep,
|
|
2400
|
+
resultStep,
|
|
2109
2401
|
invokeCount: this.invokeCount,
|
|
2110
|
-
concurrency:
|
|
2402
|
+
concurrency: NO_CONCURRENCY,
|
|
2111
2403
|
telemetry: this.telemetry,
|
|
2112
|
-
dispatchDebug: this.dispatchDebug
|
|
2113
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2404
|
+
dispatchDebug: this.dispatchDebug
|
|
2114
2405
|
});
|
|
2115
2406
|
throw new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2116
2407
|
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Submits the result of the step being held, if there is one, and
|
|
2410
|
+
* returns the abort the caller has to throw to end the invocation.
|
|
2411
|
+
*
|
|
2412
|
+
* Returns undefined when no step is being held, which is the caller's
|
|
2413
|
+
* signal that there is nothing to end. Throws instead of returning
|
|
2414
|
+
* when the submission itself fails, so that error reaches the caller
|
|
2415
|
+
* rather than an abort claiming the step was submitted.
|
|
2416
|
+
*
|
|
2417
|
+
* Callers which arrive while a submission is already under way (the
|
|
2418
|
+
* steps of a parallel group, added together) wait for that one and get
|
|
2419
|
+
* the same abort, rather than submitting a second time.
|
|
2420
|
+
*
|
|
2421
|
+
* The next step's settings are attached whenever it has any, without
|
|
2422
|
+
* checking them against the current delivery: the executor only knows
|
|
2423
|
+
* the configuration of the delivery in hand, which inside a
|
|
2424
|
+
* delivery is the *previous* step's, so such a check would be wrong
|
|
2425
|
+
* exactly when two steps with settings follow each other.
|
|
2426
|
+
*
|
|
2427
|
+
* @param nextStepSettings step-level settings of the next step
|
|
2428
|
+
* @returns `submitted-step` with the abort which ends this invocation,
|
|
2429
|
+
* or `no-pending-step` when nothing was held and there is nothing to
|
|
2430
|
+
* end
|
|
2431
|
+
*/
|
|
2432
|
+
async submitPendingStep(nextStepSettings) {
|
|
2433
|
+
if (!this.pendingStep) {
|
|
2434
|
+
return ok({ result: "no-pending-step" });
|
|
2435
|
+
}
|
|
2436
|
+
try {
|
|
2437
|
+
if (this.pendingStep.status === "submitting") {
|
|
2438
|
+
return ok({ result: "submitted-step", abort: await this.pendingStep.submitted });
|
|
2439
|
+
}
|
|
2440
|
+
const { lazyStep, resultStep } = this.pendingStep;
|
|
2441
|
+
const submitted = (async () => {
|
|
2442
|
+
await submitStepResult({
|
|
2443
|
+
context: this.context,
|
|
2444
|
+
lazyStep,
|
|
2445
|
+
resultStep,
|
|
2446
|
+
invokeCount: this.invokeCount,
|
|
2447
|
+
concurrency: NO_CONCURRENCY,
|
|
2448
|
+
telemetry: this.telemetry,
|
|
2449
|
+
dispatchDebug: this.dispatchDebug,
|
|
2450
|
+
nextStepSettings
|
|
2451
|
+
});
|
|
2452
|
+
return new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2453
|
+
})();
|
|
2454
|
+
this.pendingStep = { status: "submitting", submitted };
|
|
2455
|
+
return ok({ result: "submitted-step", abort: await submitted });
|
|
2456
|
+
} catch (error) {
|
|
2457
|
+
return err(error);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2117
2460
|
/**
|
|
2118
2461
|
* Runs steps in parallel.
|
|
2119
2462
|
*
|
|
@@ -2162,15 +2505,20 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2162
2505
|
validateStep(parallelSteps[stepIndex], planStep);
|
|
2163
2506
|
try {
|
|
2164
2507
|
const parallelStep = parallelSteps[stepIndex];
|
|
2165
|
-
const resultStep = await
|
|
2166
|
-
context: this.context,
|
|
2508
|
+
const resultStep = await executeStep({
|
|
2167
2509
|
lazyStep: parallelStep,
|
|
2168
2510
|
stepId: planStep.targetStep,
|
|
2511
|
+
concurrency: parallelSteps.length,
|
|
2512
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2513
|
+
});
|
|
2514
|
+
await submitStepResult({
|
|
2515
|
+
context: this.context,
|
|
2516
|
+
lazyStep: parallelStep,
|
|
2517
|
+
resultStep,
|
|
2169
2518
|
invokeCount: this.invokeCount,
|
|
2170
2519
|
concurrency: parallelSteps.length,
|
|
2171
2520
|
telemetry: this.telemetry,
|
|
2172
|
-
dispatchDebug: this.dispatchDebug
|
|
2173
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2521
|
+
dispatchDebug: this.dispatchDebug
|
|
2174
2522
|
});
|
|
2175
2523
|
throw new WorkflowAbort(parallelStep.stepName, resultStep);
|
|
2176
2524
|
} catch (error) {
|
|
@@ -2786,6 +3134,46 @@ var WorkflowContext = class {
|
|
|
2786
3134
|
* `1` on the first retry, `2` on the second, and so on.
|
|
2787
3135
|
*/
|
|
2788
3136
|
retried;
|
|
3137
|
+
/**
|
|
3138
|
+
* Configuration QStash applied to the request being handled.
|
|
3139
|
+
*
|
|
3140
|
+
* This is the configuration of *this delivery*, not of the run: it is
|
|
3141
|
+
* the configuration the run was triggered with, except inside the
|
|
3142
|
+
* delivery which executes a step that has step-level settings, where
|
|
3143
|
+
* it is that step's settings. The fields below expose it; the executor
|
|
3144
|
+
* gets it directly rather than reaching through the context.
|
|
3145
|
+
*/
|
|
3146
|
+
effectiveConfig;
|
|
3147
|
+
/**
|
|
3148
|
+
* Flow control QStash applied to the request being handled, if any.
|
|
3149
|
+
*
|
|
3150
|
+
* This is the flow control of the request in hand, which is the one the
|
|
3151
|
+
* run was triggered with except inside a step carrying its own.
|
|
3152
|
+
*/
|
|
3153
|
+
get flowControl() {
|
|
3154
|
+
return this.effectiveConfig.flowControl;
|
|
3155
|
+
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Retry limit QStash applied to the request being handled, if it
|
|
3158
|
+
* reported one.
|
|
3159
|
+
*
|
|
3160
|
+
* Not to be confused with {@link retried}, which is how many retries
|
|
3161
|
+
* have already happened.
|
|
3162
|
+
*
|
|
3163
|
+
* @see {@link flowControl} for what "the request being handled" means
|
|
3164
|
+
*/
|
|
3165
|
+
get retries() {
|
|
3166
|
+
return this.effectiveConfig.retries;
|
|
3167
|
+
}
|
|
3168
|
+
/**
|
|
3169
|
+
* Retry delay expression QStash applied to the request being handled,
|
|
3170
|
+
* if any.
|
|
3171
|
+
*
|
|
3172
|
+
* @see {@link flowControl} for what "the request being handled" means
|
|
3173
|
+
*/
|
|
3174
|
+
get retryDelay() {
|
|
3175
|
+
return this.effectiveConfig.retryDelay;
|
|
3176
|
+
}
|
|
2789
3177
|
constructor({
|
|
2790
3178
|
qstashClient,
|
|
2791
3179
|
workflowRunId,
|
|
@@ -2799,7 +3187,8 @@ var WorkflowContext = class {
|
|
|
2799
3187
|
invokeCount,
|
|
2800
3188
|
label,
|
|
2801
3189
|
retried,
|
|
2802
|
-
middlewareManager
|
|
3190
|
+
middlewareManager,
|
|
3191
|
+
effectiveConfig
|
|
2803
3192
|
}) {
|
|
2804
3193
|
this.qstashClient = qstashClient;
|
|
2805
3194
|
this.workflowRunId = workflowRunId;
|
|
@@ -2811,6 +3200,7 @@ var WorkflowContext = class {
|
|
|
2811
3200
|
this.env = env ?? {};
|
|
2812
3201
|
this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
|
|
2813
3202
|
this.retried = retried ?? 0;
|
|
3203
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2814
3204
|
const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
|
|
2815
3205
|
middlewareManagerInstance.assignContext(this);
|
|
2816
3206
|
this.executor = new AutoExecutor(
|
|
@@ -2819,7 +3209,8 @@ var WorkflowContext = class {
|
|
|
2819
3209
|
middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
|
|
2820
3210
|
middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
|
|
2821
3211
|
telemetry2,
|
|
2822
|
-
invokeCount
|
|
3212
|
+
invokeCount,
|
|
3213
|
+
this.effectiveConfig
|
|
2823
3214
|
);
|
|
2824
3215
|
}
|
|
2825
3216
|
/**
|
|
@@ -2845,13 +3236,35 @@ var WorkflowContext = class {
|
|
|
2845
3236
|
* ])
|
|
2846
3237
|
* ```
|
|
2847
3238
|
*
|
|
3239
|
+
* Step-level settings can be passed as a third argument, overriding
|
|
3240
|
+
* the settings the workflow run was triggered with for this step only:
|
|
3241
|
+
*
|
|
3242
|
+
* ```typescript
|
|
3243
|
+
* const result = await context.run(
|
|
3244
|
+
* "step 1",
|
|
3245
|
+
* () => {
|
|
3246
|
+
* return "result"
|
|
3247
|
+
* },
|
|
3248
|
+
* {
|
|
3249
|
+
* flowControl: { key: "custom-key", parallelism: 3 },
|
|
3250
|
+
* retries: 5,
|
|
3251
|
+
* }
|
|
3252
|
+
* )
|
|
3253
|
+
* ```
|
|
3254
|
+
*
|
|
2848
3255
|
* @param stepName name of the step
|
|
2849
3256
|
* @param stepFunction step function to be executed
|
|
3257
|
+
* @param stepSettings step-level settings for this step
|
|
2850
3258
|
* @returns result of the step function
|
|
2851
3259
|
*/
|
|
2852
|
-
async run(stepName, stepFunction) {
|
|
3260
|
+
async run(stepName, stepFunction, stepSettings) {
|
|
3261
|
+
if (stepSettings) {
|
|
3262
|
+
validateFlowControl(stepSettings.flowControl);
|
|
3263
|
+
}
|
|
2853
3264
|
const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
|
|
2854
|
-
return await this.addStep(
|
|
3265
|
+
return await this.addStep(
|
|
3266
|
+
new LazyFunctionStep(this, stepName, wrappedStepFunction, stepSettings)
|
|
3267
|
+
);
|
|
2855
3268
|
}
|
|
2856
3269
|
/**
|
|
2857
3270
|
* Stops the execution for the duration provided.
|
|
@@ -3074,7 +3487,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3074
3487
|
*
|
|
3075
3488
|
* @param routeFunction
|
|
3076
3489
|
*/
|
|
3077
|
-
static async tryAuthentication(routeFunction, context) {
|
|
3490
|
+
static async tryAuthentication(routeFunction, context, effectiveConfig) {
|
|
3078
3491
|
const disabledContext = new _DisabledWorkflowContext({
|
|
3079
3492
|
qstashClient: new import_qstash10.Client({
|
|
3080
3493
|
baseUrl: "disabled-client",
|
|
@@ -3089,7 +3502,10 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3089
3502
|
initialPayload: context.requestPayload,
|
|
3090
3503
|
env: context.env,
|
|
3091
3504
|
label: context.labels,
|
|
3092
|
-
retried: context.retried
|
|
3505
|
+
retried: context.retried,
|
|
3506
|
+
// the route function runs for real here until it reaches a step, so
|
|
3507
|
+
// it observes the same configuration as it will on the real context
|
|
3508
|
+
effectiveConfig
|
|
3093
3509
|
});
|
|
3094
3510
|
try {
|
|
3095
3511
|
await routeFunction(disabledContext);
|
|
@@ -3306,6 +3722,7 @@ var handleFailure = async ({
|
|
|
3306
3722
|
}
|
|
3307
3723
|
const userHeaders = recreateUserHeaders(request.headers);
|
|
3308
3724
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3725
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3309
3726
|
const workflowContext = new WorkflowContext({
|
|
3310
3727
|
qstashClient,
|
|
3311
3728
|
workflowRunId,
|
|
@@ -3319,11 +3736,13 @@ var handleFailure = async ({
|
|
|
3319
3736
|
label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
|
|
3320
3737
|
retried,
|
|
3321
3738
|
workflowRunCreatedAt: workflowCreatedAt,
|
|
3322
|
-
middlewareManager: void 0
|
|
3739
|
+
middlewareManager: void 0,
|
|
3740
|
+
effectiveConfig
|
|
3323
3741
|
});
|
|
3324
3742
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3325
3743
|
routeFunction,
|
|
3326
|
-
workflowContext
|
|
3744
|
+
workflowContext,
|
|
3745
|
+
effectiveConfig
|
|
3327
3746
|
);
|
|
3328
3747
|
if (authCheck.isErr()) {
|
|
3329
3748
|
await dispatchDebug?.("onError", {
|
|
@@ -3415,7 +3834,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
|
|
|
3415
3834
|
const client = new import_qstash11.Client({
|
|
3416
3835
|
...clientOptions,
|
|
3417
3836
|
baseUrl: clientEnv.QSTASH_URL,
|
|
3418
|
-
token: clientEnv.QSTASH_TOKEN
|
|
3837
|
+
token: clientEnv.QSTASH_TOKEN,
|
|
3838
|
+
// Mirror the receiver's dev-mode decision so a single QSTASH_DEV=true points
|
|
3839
|
+
// the client at the local dev server too. Without this the QStash Client
|
|
3840
|
+
// re-derives dev mode from its own process.env, which is blind to the
|
|
3841
|
+
// Cloudflare worker `env` binding that actually carries QSTASH_DEV there.
|
|
3842
|
+
// An explicit devMode in the user's client config still wins.
|
|
3843
|
+
devMode: clientOptions?.devMode ?? isQStashDevModeEnabled(environment)
|
|
3419
3844
|
});
|
|
3420
3845
|
const receiver = getReceiver(environment, receiverConfig, region);
|
|
3421
3846
|
return { client, receiver };
|
|
@@ -3465,7 +3890,9 @@ var getQStashHandlers = ({
|
|
|
3465
3890
|
client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
|
|
3466
3891
|
...qstashClientOption,
|
|
3467
3892
|
baseUrl: environment.QSTASH_URL,
|
|
3468
|
-
token: environment.QSTASH_TOKEN
|
|
3893
|
+
token: environment.QSTASH_TOKEN,
|
|
3894
|
+
// Mirror the receiver's dev-mode decision (see createRegionalHandler).
|
|
3895
|
+
devMode: qstashClientOption?.devMode ?? isQStashDevModeEnabled(environment)
|
|
3469
3896
|
}),
|
|
3470
3897
|
receiver: getReceiver(environment, receiverConfig)
|
|
3471
3898
|
}
|
|
@@ -3805,12 +4232,14 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
3805
4232
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3806
4233
|
const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
|
|
3807
4234
|
const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
4235
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3808
4236
|
const workflowContext = new WorkflowContext({
|
|
3809
4237
|
qstashClient: regionalClient,
|
|
3810
4238
|
workflowRunId,
|
|
3811
4239
|
initialPayload: isThirdPartyCallResult(request) ? JSON.parse(rawInitialPayload) : initialPayloadParser(rawInitialPayload),
|
|
3812
4240
|
headers: recreateUserHeaders(request.headers),
|
|
3813
4241
|
steps,
|
|
4242
|
+
effectiveConfig,
|
|
3814
4243
|
url: workflowUrl,
|
|
3815
4244
|
env,
|
|
3816
4245
|
telemetry: telemetry2,
|
|
@@ -3822,7 +4251,8 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
3822
4251
|
});
|
|
3823
4252
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3824
4253
|
routeFunction,
|
|
3825
|
-
workflowContext
|
|
4254
|
+
workflowContext,
|
|
4255
|
+
effectiveConfig
|
|
3826
4256
|
);
|
|
3827
4257
|
if (authCheck.isErr()) {
|
|
3828
4258
|
throw authCheck.error;
|
|
@@ -3859,7 +4289,23 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
3859
4289
|
if (steps.length === 1) {
|
|
3860
4290
|
await middlewareManager.dispatchLifecycle("runStarted", {});
|
|
3861
4291
|
}
|
|
3862
|
-
|
|
4292
|
+
let outcome;
|
|
4293
|
+
try {
|
|
4294
|
+
outcome = { ran: true, result: await routeFunction(workflowContext) };
|
|
4295
|
+
} catch (error) {
|
|
4296
|
+
outcome = { ran: false, error };
|
|
4297
|
+
}
|
|
4298
|
+
const submitted = await flushPendingStep(workflowContext);
|
|
4299
|
+
if (submitted.isErr()) {
|
|
4300
|
+
throw submitted.error;
|
|
4301
|
+
}
|
|
4302
|
+
if (submitted.value.result === "submitted-step") {
|
|
4303
|
+
throw submitted.value.abort;
|
|
4304
|
+
}
|
|
4305
|
+
if (!outcome.ran) {
|
|
4306
|
+
throw outcome.error;
|
|
4307
|
+
}
|
|
4308
|
+
return outcome.result;
|
|
3863
4309
|
},
|
|
3864
4310
|
onCleanup: async (result2) => {
|
|
3865
4311
|
await middlewareManager.dispatchLifecycle("runCompleted", {
|
|
@@ -3941,6 +4387,7 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
3941
4387
|
|
|
3942
4388
|
// platforms/express.ts
|
|
3943
4389
|
var import_express = require("express");
|
|
4390
|
+
var import_qstash13 = require("@upstash/qstash");
|
|
3944
4391
|
var isEmptyRequest = (req) => {
|
|
3945
4392
|
return req.headers["content-type"] === "application/json" && req.headers["content-length"] === "0";
|
|
3946
4393
|
};
|
|
@@ -3991,6 +4438,7 @@ function createExpressHandler(params) {
|
|
|
3991
4438
|
};
|
|
3992
4439
|
}
|
|
3993
4440
|
function serve(routeFunction, options) {
|
|
4441
|
+
void (0, import_qstash13.startDevServer)();
|
|
3994
4442
|
const router = (0, import_express.Router)();
|
|
3995
4443
|
const handler = createExpressHandler([routeFunction, options]);
|
|
3996
4444
|
router.all(/(.*)/, handler);
|
|
@@ -4006,6 +4454,7 @@ var createWorkflow = (...params) => {
|
|
|
4006
4454
|
};
|
|
4007
4455
|
};
|
|
4008
4456
|
var serveMany = (workflows, options) => {
|
|
4457
|
+
void (0, import_qstash13.startDevServer)();
|
|
4009
4458
|
const router = (0, import_express.Router)();
|
|
4010
4459
|
const { handler } = serveManyBase({
|
|
4011
4460
|
workflows,
|