@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/nextjs.js
CHANGED
|
@@ -29,6 +29,9 @@ __export(nextjs_exports, {
|
|
|
29
29
|
});
|
|
30
30
|
module.exports = __toCommonJS(nextjs_exports);
|
|
31
31
|
|
|
32
|
+
// src/serve/index.ts
|
|
33
|
+
var import_qstash12 = require("@upstash/qstash");
|
|
34
|
+
|
|
32
35
|
// src/client/utils.ts
|
|
33
36
|
var import_qstash2 = require("@upstash/qstash");
|
|
34
37
|
|
|
@@ -217,6 +220,14 @@ var WORKFLOW_CREATED_AT_HEADER = "Upstash-Workflow-CreatedAt";
|
|
|
217
220
|
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
|
|
218
221
|
var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
|
|
219
222
|
var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
|
|
223
|
+
var WORKFLOW_FEATURE_SET = "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig";
|
|
224
|
+
var WORKFLOW_STEP_CONFIG_CALL_TYPE = "stepConfig";
|
|
225
|
+
var WORKFLOW_STEP_CONFIG_HEADER = "Upstash-Workflow-Step-Config";
|
|
226
|
+
var FLOW_CONTROL_KEY_HEADER = "Upstash-Flow-Control-Key";
|
|
227
|
+
var FLOW_CONTROL_VALUE_HEADER = "Upstash-Flow-Control-Value";
|
|
228
|
+
var RETRIES_HEADER = "Upstash-Retries";
|
|
229
|
+
var RETRY_DELAY_HEADER = "Upstash-Retry-Delay";
|
|
230
|
+
var WORKFLOW_STEP_CONFIG_FEATURE = "WF_StepConfig";
|
|
220
231
|
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
|
|
221
232
|
var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
|
|
222
233
|
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
|
|
@@ -234,102 +245,6 @@ var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
|
|
|
234
245
|
var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
|
|
235
246
|
var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
|
|
236
247
|
|
|
237
|
-
// src/context/auto-executor.ts
|
|
238
|
-
var import_qstash5 = require("@upstash/qstash");
|
|
239
|
-
|
|
240
|
-
// src/qstash/headers.ts
|
|
241
|
-
var import_qstash4 = require("@upstash/qstash");
|
|
242
|
-
|
|
243
|
-
// src/utils.ts
|
|
244
|
-
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
245
|
-
var NANOID_LENGTH = 21;
|
|
246
|
-
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
247
|
-
function validateLabel(label) {
|
|
248
|
-
if (label === void 0) return;
|
|
249
|
-
const labels = Array.isArray(label) ? label : [label];
|
|
250
|
-
if (labels.length === 0) {
|
|
251
|
-
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
252
|
-
}
|
|
253
|
-
for (const value of labels) {
|
|
254
|
-
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
255
|
-
throw new WorkflowNonRetryableError(
|
|
256
|
-
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function serializeLabel(label) {
|
|
262
|
-
return Array.isArray(label) ? label.join(",") : label;
|
|
263
|
-
}
|
|
264
|
-
function validateFlowControl(flowControl) {
|
|
265
|
-
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
266
|
-
throw new WorkflowNonRetryableError(
|
|
267
|
-
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
function getRandomInt() {
|
|
272
|
-
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
273
|
-
}
|
|
274
|
-
function nanoid(length = NANOID_LENGTH) {
|
|
275
|
-
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
276
|
-
}
|
|
277
|
-
function getWorkflowRunId(id) {
|
|
278
|
-
return `wfr_${id ?? nanoid()}`;
|
|
279
|
-
}
|
|
280
|
-
function decodeBase64(base64) {
|
|
281
|
-
const binString = atob(base64);
|
|
282
|
-
try {
|
|
283
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
284
|
-
return new TextDecoder().decode(intArray);
|
|
285
|
-
} catch (error) {
|
|
286
|
-
console.warn(
|
|
287
|
-
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
288
|
-
);
|
|
289
|
-
return binString;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
function getUserIdFromToken(qstashClient) {
|
|
293
|
-
try {
|
|
294
|
-
const token = qstashClient.token;
|
|
295
|
-
const decodedToken = decodeBase64(token);
|
|
296
|
-
const tokenPayload = JSON.parse(decodedToken);
|
|
297
|
-
const userId = tokenPayload.UserID;
|
|
298
|
-
if (!userId) {
|
|
299
|
-
throw new WorkflowError("QStash token payload does not contain userId");
|
|
300
|
-
}
|
|
301
|
-
return userId;
|
|
302
|
-
} catch (error) {
|
|
303
|
-
throw new WorkflowError(
|
|
304
|
-
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
function getQStashUrl(qstashClient) {
|
|
309
|
-
try {
|
|
310
|
-
const requester = qstashClient.http;
|
|
311
|
-
const baseUrl = requester.baseUrl;
|
|
312
|
-
if (!baseUrl) {
|
|
313
|
-
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
314
|
-
}
|
|
315
|
-
return baseUrl;
|
|
316
|
-
} catch (error) {
|
|
317
|
-
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
function getEventId() {
|
|
321
|
-
return `evt_${nanoid(15)}`;
|
|
322
|
-
}
|
|
323
|
-
function stringifyBody(body) {
|
|
324
|
-
if (body === void 0) {
|
|
325
|
-
return void 0;
|
|
326
|
-
}
|
|
327
|
-
if (typeof body === "string") {
|
|
328
|
-
return body;
|
|
329
|
-
}
|
|
330
|
-
return JSON.stringify(body);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
248
|
// node_modules/neverthrow/dist/index.es.js
|
|
334
249
|
var defaultErrorConfig = {
|
|
335
250
|
withStackTrace: false
|
|
@@ -745,6 +660,102 @@ var Err = class {
|
|
|
745
660
|
};
|
|
746
661
|
var fromThrowable = Result.fromThrowable;
|
|
747
662
|
|
|
663
|
+
// src/context/auto-executor.ts
|
|
664
|
+
var import_qstash5 = require("@upstash/qstash");
|
|
665
|
+
|
|
666
|
+
// src/qstash/headers.ts
|
|
667
|
+
var import_qstash4 = require("@upstash/qstash");
|
|
668
|
+
|
|
669
|
+
// src/utils.ts
|
|
670
|
+
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
671
|
+
var NANOID_LENGTH = 21;
|
|
672
|
+
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
673
|
+
function validateLabel(label) {
|
|
674
|
+
if (label === void 0) return;
|
|
675
|
+
const labels = Array.isArray(label) ? label : [label];
|
|
676
|
+
if (labels.length === 0) {
|
|
677
|
+
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
678
|
+
}
|
|
679
|
+
for (const value of labels) {
|
|
680
|
+
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
681
|
+
throw new WorkflowNonRetryableError(
|
|
682
|
+
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function serializeLabel(label) {
|
|
688
|
+
return Array.isArray(label) ? label.join(",") : label;
|
|
689
|
+
}
|
|
690
|
+
function validateFlowControl(flowControl) {
|
|
691
|
+
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
692
|
+
throw new WorkflowNonRetryableError(
|
|
693
|
+
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function getRandomInt() {
|
|
698
|
+
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
699
|
+
}
|
|
700
|
+
function nanoid(length = NANOID_LENGTH) {
|
|
701
|
+
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
702
|
+
}
|
|
703
|
+
function getWorkflowRunId(id) {
|
|
704
|
+
return `wfr_${id ?? nanoid()}`;
|
|
705
|
+
}
|
|
706
|
+
function decodeBase64(base64) {
|
|
707
|
+
const binString = atob(base64);
|
|
708
|
+
try {
|
|
709
|
+
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
710
|
+
return new TextDecoder().decode(intArray);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
console.warn(
|
|
713
|
+
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
714
|
+
);
|
|
715
|
+
return binString;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function getUserIdFromToken(qstashClient) {
|
|
719
|
+
try {
|
|
720
|
+
const token = qstashClient.token;
|
|
721
|
+
const decodedToken = decodeBase64(token);
|
|
722
|
+
const tokenPayload = JSON.parse(decodedToken);
|
|
723
|
+
const userId = tokenPayload.UserID;
|
|
724
|
+
if (!userId) {
|
|
725
|
+
throw new WorkflowError("QStash token payload does not contain userId");
|
|
726
|
+
}
|
|
727
|
+
return userId;
|
|
728
|
+
} catch (error) {
|
|
729
|
+
throw new WorkflowError(
|
|
730
|
+
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function getQStashUrl(qstashClient) {
|
|
735
|
+
try {
|
|
736
|
+
const requester = qstashClient.http;
|
|
737
|
+
const baseUrl = requester.baseUrl;
|
|
738
|
+
if (!baseUrl) {
|
|
739
|
+
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
740
|
+
}
|
|
741
|
+
return baseUrl;
|
|
742
|
+
} catch (error) {
|
|
743
|
+
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function getEventId() {
|
|
747
|
+
return `evt_${nanoid(15)}`;
|
|
748
|
+
}
|
|
749
|
+
function stringifyBody(body) {
|
|
750
|
+
if (body === void 0) {
|
|
751
|
+
return void 0;
|
|
752
|
+
}
|
|
753
|
+
if (typeof body === "string") {
|
|
754
|
+
return body;
|
|
755
|
+
}
|
|
756
|
+
return JSON.stringify(body);
|
|
757
|
+
}
|
|
758
|
+
|
|
748
759
|
// src/types.ts
|
|
749
760
|
var StepTypes = [
|
|
750
761
|
"Initial",
|
|
@@ -850,6 +861,10 @@ var triggerFirstInvocation = async (params) => {
|
|
|
850
861
|
return err(error_);
|
|
851
862
|
}
|
|
852
863
|
};
|
|
864
|
+
var flushPendingStep = async (workflowContext) => {
|
|
865
|
+
const { executor } = workflowContext;
|
|
866
|
+
return await executor.submitPendingStep();
|
|
867
|
+
};
|
|
853
868
|
var triggerRouteFunction = async ({
|
|
854
869
|
onCleanup,
|
|
855
870
|
onStep,
|
|
@@ -1057,8 +1072,29 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
|
|
|
1057
1072
|
var BaseLazyStep = class _BaseLazyStep {
|
|
1058
1073
|
stepName;
|
|
1059
1074
|
context;
|
|
1060
|
-
|
|
1075
|
+
/**
|
|
1076
|
+
* step-level settings (flow control, retries etc.) which override the
|
|
1077
|
+
* settings the workflow run was triggered with, for this step only.
|
|
1078
|
+
*/
|
|
1079
|
+
stepSettings;
|
|
1080
|
+
/**
|
|
1081
|
+
* whether this step's result can be submitted after the route function
|
|
1082
|
+
* has moved on, instead of right away.
|
|
1083
|
+
*
|
|
1084
|
+
* Enabled for steps whose `getResultStep` produces the final `out` —
|
|
1085
|
+
* which is not the same as "needs no server round trip":
|
|
1086
|
+
* `LazyCreateWebhookStep` builds its result in `getBody` and returns
|
|
1087
|
+
* `out: undefined` from `getResultStep`, so deferring it would hand
|
|
1088
|
+
* `undefined` to the route function.
|
|
1089
|
+
*
|
|
1090
|
+
* Deferring lets the route function continue and reveal the next step,
|
|
1091
|
+
* so that step's settings can ride on this step's submission instead
|
|
1092
|
+
* of needing a step config request of their own.
|
|
1093
|
+
*/
|
|
1094
|
+
supportsDeferredSubmission = false;
|
|
1095
|
+
constructor(context, stepName, stepSettings) {
|
|
1061
1096
|
this.context = context;
|
|
1097
|
+
this.stepSettings = stepSettings;
|
|
1062
1098
|
if (!stepName) {
|
|
1063
1099
|
throw new WorkflowError(
|
|
1064
1100
|
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
|
|
@@ -1122,7 +1158,13 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1122
1158
|
step.out = JSON.stringify(step.out);
|
|
1123
1159
|
return JSON.stringify(step);
|
|
1124
1160
|
}
|
|
1125
|
-
getHeaders({
|
|
1161
|
+
getHeaders({
|
|
1162
|
+
context,
|
|
1163
|
+
telemetry,
|
|
1164
|
+
invokeCount,
|
|
1165
|
+
step,
|
|
1166
|
+
stepSettings
|
|
1167
|
+
}) {
|
|
1126
1168
|
return getHeaders({
|
|
1127
1169
|
initHeaderValue: "false",
|
|
1128
1170
|
workflowConfig: {
|
|
@@ -1135,7 +1177,8 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1135
1177
|
stepInfo: {
|
|
1136
1178
|
step,
|
|
1137
1179
|
lazyStep: this
|
|
1138
|
-
}
|
|
1180
|
+
},
|
|
1181
|
+
stepSettings
|
|
1139
1182
|
});
|
|
1140
1183
|
}
|
|
1141
1184
|
async submitStep({ context, body, headers }) {
|
|
@@ -1153,8 +1196,9 @@ var LazyFunctionStep = class extends BaseLazyStep {
|
|
|
1153
1196
|
stepFunction;
|
|
1154
1197
|
stepType = "Run";
|
|
1155
1198
|
allowUndefinedOut = true;
|
|
1156
|
-
|
|
1157
|
-
|
|
1199
|
+
supportsDeferredSubmission = true;
|
|
1200
|
+
constructor(context, stepName, stepFunction, stepSettings) {
|
|
1201
|
+
super(context, stepName, stepSettings);
|
|
1158
1202
|
this.stepFunction = stepFunction;
|
|
1159
1203
|
}
|
|
1160
1204
|
getPlanStep(concurrent, targetStep) {
|
|
@@ -1184,6 +1228,7 @@ var LazySleepStep = class extends BaseLazyStep {
|
|
|
1184
1228
|
sleep;
|
|
1185
1229
|
stepType = "SleepFor";
|
|
1186
1230
|
allowUndefinedOut = true;
|
|
1231
|
+
supportsDeferredSubmission = true;
|
|
1187
1232
|
constructor(context, stepName, sleep) {
|
|
1188
1233
|
super(context, stepName);
|
|
1189
1234
|
this.sleep = sleep;
|
|
@@ -1223,6 +1268,7 @@ var LazySleepUntilStep = class extends BaseLazyStep {
|
|
|
1223
1268
|
sleepUntil;
|
|
1224
1269
|
stepType = "SleepUntil";
|
|
1225
1270
|
allowUndefinedOut = true;
|
|
1271
|
+
supportsDeferredSubmission = true;
|
|
1226
1272
|
constructor(context, stepName, sleepUntil) {
|
|
1227
1273
|
super(context, stepName);
|
|
1228
1274
|
this.sleepUntil = sleepUntil;
|
|
@@ -1715,6 +1761,7 @@ var WorkflowHeaders = class {
|
|
|
1715
1761
|
invokeCount;
|
|
1716
1762
|
initHeaderValue;
|
|
1717
1763
|
stepInfo;
|
|
1764
|
+
stepSettings;
|
|
1718
1765
|
headers;
|
|
1719
1766
|
/**
|
|
1720
1767
|
* @param params workflow header parameters
|
|
@@ -1724,17 +1771,20 @@ var WorkflowHeaders = class {
|
|
|
1724
1771
|
workflowConfig,
|
|
1725
1772
|
invokeCount,
|
|
1726
1773
|
initHeaderValue,
|
|
1727
|
-
stepInfo
|
|
1774
|
+
stepInfo,
|
|
1775
|
+
stepSettings
|
|
1728
1776
|
}) {
|
|
1729
1777
|
this.userHeaders = userHeaders;
|
|
1730
1778
|
this.workflowConfig = workflowConfig;
|
|
1731
1779
|
this.invokeCount = invokeCount;
|
|
1732
1780
|
this.initHeaderValue = initHeaderValue;
|
|
1733
1781
|
this.stepInfo = stepInfo;
|
|
1782
|
+
this.stepSettings = stepSettings;
|
|
1734
1783
|
this.headers = {
|
|
1735
1784
|
rawHeaders: {},
|
|
1736
1785
|
workflowHeaders: {},
|
|
1737
|
-
failureHeaders: {}
|
|
1786
|
+
failureHeaders: {},
|
|
1787
|
+
stepSettingsHeaders: {}
|
|
1738
1788
|
};
|
|
1739
1789
|
}
|
|
1740
1790
|
getHeaders() {
|
|
@@ -1745,6 +1795,7 @@ var WorkflowHeaders = class {
|
|
|
1745
1795
|
this.addUserHeaders();
|
|
1746
1796
|
this.addInvokeCount();
|
|
1747
1797
|
this.addFailureUrl();
|
|
1798
|
+
this.addStepSettings();
|
|
1748
1799
|
const contentType = this.addContentType();
|
|
1749
1800
|
return this.prefixHeaders(contentType);
|
|
1750
1801
|
}
|
|
@@ -1754,7 +1805,7 @@ var WorkflowHeaders = class {
|
|
|
1754
1805
|
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
|
|
1755
1806
|
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
|
|
1756
1807
|
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
|
|
1757
|
-
[WORKFLOW_FEATURE_HEADER]:
|
|
1808
|
+
[WORKFLOW_FEATURE_HEADER]: WORKFLOW_FEATURE_SET,
|
|
1758
1809
|
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
1759
1810
|
...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
|
|
1760
1811
|
};
|
|
@@ -1832,7 +1883,7 @@ var WorkflowHeaders = class {
|
|
|
1832
1883
|
this.headers.failureHeaders["Workflow-Init"] = "false";
|
|
1833
1884
|
this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
|
|
1834
1885
|
this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
|
|
1835
|
-
this.headers.failureHeaders["Feature-Set"] =
|
|
1886
|
+
this.headers.failureHeaders["Feature-Set"] = WORKFLOW_FEATURE_SET;
|
|
1836
1887
|
if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
|
|
1837
1888
|
this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
|
|
1838
1889
|
}
|
|
@@ -1840,6 +1891,42 @@ var WorkflowHeaders = class {
|
|
|
1840
1891
|
this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
|
|
1841
1892
|
}
|
|
1842
1893
|
}
|
|
1894
|
+
/**
|
|
1895
|
+
* Applies the step-level settings of a step to the message.
|
|
1896
|
+
*
|
|
1897
|
+
* These headers configure the message itself, so that QStash uses them
|
|
1898
|
+
* instead of the settings the run was triggered with. The delivery of
|
|
1899
|
+
* the message is what executes the step, which is how a step's
|
|
1900
|
+
* settings reach it.
|
|
1901
|
+
*
|
|
1902
|
+
* When any setting is present the feature set is extended with
|
|
1903
|
+
* `WF_StepConfig`, which is what tells QStash to keep them. QStash
|
|
1904
|
+
* reports that back on the delivery as `Upstash-Workflow-Step-Config`.
|
|
1905
|
+
*/
|
|
1906
|
+
addStepSettings() {
|
|
1907
|
+
if (!this.stepSettings) {
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
const headers = {};
|
|
1911
|
+
if (this.stepSettings.flowControl) {
|
|
1912
|
+
const { flowControlKey, flowControlValue } = prepareFlowControl(
|
|
1913
|
+
this.stepSettings.flowControl
|
|
1914
|
+
);
|
|
1915
|
+
headers[FLOW_CONTROL_KEY_HEADER] = flowControlKey;
|
|
1916
|
+
headers[FLOW_CONTROL_VALUE_HEADER] = flowControlValue;
|
|
1917
|
+
}
|
|
1918
|
+
if (this.stepSettings.retries !== void 0) {
|
|
1919
|
+
headers[RETRIES_HEADER] = this.stepSettings.retries.toString();
|
|
1920
|
+
}
|
|
1921
|
+
if (this.stepSettings.retryDelay) {
|
|
1922
|
+
headers[RETRY_DELAY_HEADER] = this.stepSettings.retryDelay;
|
|
1923
|
+
}
|
|
1924
|
+
if (Object.keys(headers).length === 0) {
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
headers[WORKFLOW_FEATURE_HEADER] = `${WORKFLOW_FEATURE_SET},${WORKFLOW_STEP_CONFIG_FEATURE}`;
|
|
1928
|
+
this.headers.stepSettingsHeaders = headers;
|
|
1929
|
+
}
|
|
1843
1930
|
addContentType() {
|
|
1844
1931
|
if (this.workflowConfig.useJSONContent) {
|
|
1845
1932
|
this.headers.rawHeaders["content-type"] = "application/json";
|
|
@@ -1853,14 +1940,17 @@ var WorkflowHeaders = class {
|
|
|
1853
1940
|
return contentType;
|
|
1854
1941
|
}
|
|
1855
1942
|
prefixHeaders(contentType) {
|
|
1856
|
-
const { rawHeaders, workflowHeaders, failureHeaders } = this.headers;
|
|
1943
|
+
const { rawHeaders, workflowHeaders, failureHeaders, stepSettingsHeaders } = this.headers;
|
|
1857
1944
|
const isCall = this.stepInfo?.lazyStep.stepType === "Call";
|
|
1858
1945
|
return {
|
|
1859
1946
|
headers: {
|
|
1860
1947
|
...rawHeaders,
|
|
1861
1948
|
...addPrefixToHeaders(workflowHeaders, isCall ? "Upstash-Callback-" : "Upstash-"),
|
|
1862
1949
|
...addPrefixToHeaders(failureHeaders, "Upstash-Failure-Callback-"),
|
|
1863
|
-
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {}
|
|
1950
|
+
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {},
|
|
1951
|
+
// last, so they override the run's settings above. Never
|
|
1952
|
+
// prefixed: they configure this message, not its callback.
|
|
1953
|
+
...stepSettingsHeaders
|
|
1864
1954
|
},
|
|
1865
1955
|
contentType
|
|
1866
1956
|
};
|
|
@@ -1911,7 +2001,7 @@ var submitParallelSteps = async ({
|
|
|
1911
2001
|
info: `Submitting ${planSteps.length} parallel steps.`
|
|
1912
2002
|
});
|
|
1913
2003
|
const result = await context.qstashClient.batch(
|
|
1914
|
-
planSteps.map((planStep) => {
|
|
2004
|
+
planSteps.map((planStep, index) => {
|
|
1915
2005
|
const { headers } = getHeaders({
|
|
1916
2006
|
initHeaderValue: "false",
|
|
1917
2007
|
workflowConfig: {
|
|
@@ -1919,7 +2009,8 @@ var submitParallelSteps = async ({
|
|
|
1919
2009
|
workflowUrl: context.url,
|
|
1920
2010
|
telemetry
|
|
1921
2011
|
},
|
|
1922
|
-
invokeCount
|
|
2012
|
+
invokeCount,
|
|
2013
|
+
stepSettings: steps[index].stepSettings
|
|
1923
2014
|
});
|
|
1924
2015
|
return {
|
|
1925
2016
|
headers,
|
|
@@ -1938,31 +2029,38 @@ var submitParallelSteps = async ({
|
|
|
1938
2029
|
}
|
|
1939
2030
|
throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
|
|
1940
2031
|
};
|
|
1941
|
-
var
|
|
1942
|
-
context,
|
|
2032
|
+
var executeStep = async ({
|
|
1943
2033
|
lazyStep,
|
|
1944
2034
|
stepId,
|
|
1945
|
-
invokeCount,
|
|
1946
2035
|
concurrency,
|
|
1947
|
-
telemetry,
|
|
1948
|
-
dispatchDebug,
|
|
1949
2036
|
dispatchLifecycle
|
|
1950
2037
|
}) => {
|
|
1951
2038
|
await dispatchLifecycle("beforeExecution", {
|
|
1952
2039
|
stepName: lazyStep.stepName
|
|
1953
2040
|
});
|
|
1954
|
-
let resultStep;
|
|
1955
2041
|
try {
|
|
1956
|
-
|
|
2042
|
+
return await lazyStep.getResultStep(concurrency, stepId);
|
|
1957
2043
|
} catch (error) {
|
|
1958
2044
|
attachStepNameToError(error, lazyStep.stepName);
|
|
1959
2045
|
throw error;
|
|
1960
2046
|
}
|
|
2047
|
+
};
|
|
2048
|
+
var submitStepResult = async ({
|
|
2049
|
+
context,
|
|
2050
|
+
lazyStep,
|
|
2051
|
+
resultStep,
|
|
2052
|
+
invokeCount,
|
|
2053
|
+
concurrency,
|
|
2054
|
+
telemetry,
|
|
2055
|
+
dispatchDebug,
|
|
2056
|
+
nextStepSettings
|
|
2057
|
+
}) => {
|
|
1961
2058
|
const { headers } = lazyStep.getHeaders({
|
|
1962
2059
|
context,
|
|
1963
2060
|
step: resultStep,
|
|
1964
2061
|
invokeCount,
|
|
1965
|
-
telemetry
|
|
2062
|
+
telemetry,
|
|
2063
|
+
stepSettings: nextStepSettings
|
|
1966
2064
|
});
|
|
1967
2065
|
const body = lazyStep.getBody({
|
|
1968
2066
|
context,
|
|
@@ -1987,6 +2085,138 @@ var submitSingleStep = async ({
|
|
|
1987
2085
|
}
|
|
1988
2086
|
return resultStep;
|
|
1989
2087
|
};
|
|
2088
|
+
var publishStepConfigRequest = async ({
|
|
2089
|
+
context,
|
|
2090
|
+
lazyStep,
|
|
2091
|
+
targetStep,
|
|
2092
|
+
invokeCount,
|
|
2093
|
+
telemetry,
|
|
2094
|
+
dispatchDebug
|
|
2095
|
+
}) => {
|
|
2096
|
+
const { headers } = getHeaders({
|
|
2097
|
+
initHeaderValue: "false",
|
|
2098
|
+
workflowConfig: {
|
|
2099
|
+
workflowRunId: context.workflowRunId,
|
|
2100
|
+
workflowUrl: context.url,
|
|
2101
|
+
telemetry
|
|
2102
|
+
},
|
|
2103
|
+
invokeCount,
|
|
2104
|
+
stepSettings: lazyStep.stepSettings
|
|
2105
|
+
});
|
|
2106
|
+
await dispatchDebug("onInfo", {
|
|
2107
|
+
info: `Step "${lazyStep.stepName}" (${targetStep}) has step-level settings which the current delivery does not carry. Requesting a delivery which does.`
|
|
2108
|
+
});
|
|
2109
|
+
const result = await context.qstashClient.publishJSON({
|
|
2110
|
+
headers: {
|
|
2111
|
+
...headers,
|
|
2112
|
+
"Upstash-Workflow-CallType": WORKFLOW_STEP_CONFIG_CALL_TYPE
|
|
2113
|
+
},
|
|
2114
|
+
method: "POST",
|
|
2115
|
+
body: { targetStep, invokeCount },
|
|
2116
|
+
url: context.url,
|
|
2117
|
+
contentBasedDeduplication: true
|
|
2118
|
+
});
|
|
2119
|
+
if (result?.deduplicated) {
|
|
2120
|
+
await dispatchDebug("onWarning", {
|
|
2121
|
+
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.`
|
|
2122
|
+
});
|
|
2123
|
+
} else if (result?.messageId) {
|
|
2124
|
+
await dispatchDebug("onInfo", {
|
|
2125
|
+
info: `Requested a delivery under the settings of step "${lazyStep.stepName}" with messageId: ${result.messageId}.`
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2129
|
+
|
|
2130
|
+
// src/qstash/step-config.ts
|
|
2131
|
+
var DEFAULT_PARALLELISM = 0;
|
|
2132
|
+
var DEFAULT_RATE = 0;
|
|
2133
|
+
var DEFAULT_PERIOD_SECONDS = 1;
|
|
2134
|
+
var SECONDS_PER_UNIT = {
|
|
2135
|
+
s: 1,
|
|
2136
|
+
m: 60,
|
|
2137
|
+
h: 60 * 60,
|
|
2138
|
+
d: 24 * 60 * 60
|
|
2139
|
+
};
|
|
2140
|
+
var durationToSeconds = (duration) => {
|
|
2141
|
+
if (typeof duration === "number") {
|
|
2142
|
+
return Number.isFinite(duration) ? duration : void 0;
|
|
2143
|
+
}
|
|
2144
|
+
const match = /^(\d+)([smhd])$/.exec(duration.trim());
|
|
2145
|
+
if (match) {
|
|
2146
|
+
return Number(match[1]) * SECONDS_PER_UNIT[match[2]];
|
|
2147
|
+
}
|
|
2148
|
+
const seconds = Number(duration.trim());
|
|
2149
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2150
|
+
};
|
|
2151
|
+
var normalizeFlowControl = (flowControl) => {
|
|
2152
|
+
const period = flowControl.period === void 0 ? void 0 : durationToSeconds(flowControl.period);
|
|
2153
|
+
return {
|
|
2154
|
+
key: flowControl.key,
|
|
2155
|
+
parallelism: flowControl.parallelism ?? DEFAULT_PARALLELISM,
|
|
2156
|
+
rate: flowControl.rate ?? flowControl.ratePerSecond ?? DEFAULT_RATE,
|
|
2157
|
+
period: period ?? DEFAULT_PERIOD_SECONDS
|
|
2158
|
+
};
|
|
2159
|
+
};
|
|
2160
|
+
var parseFlowControlHeaders = (key, value) => {
|
|
2161
|
+
if (!key) {
|
|
2162
|
+
return void 0;
|
|
2163
|
+
}
|
|
2164
|
+
const flowControl = {
|
|
2165
|
+
key,
|
|
2166
|
+
parallelism: DEFAULT_PARALLELISM,
|
|
2167
|
+
rate: DEFAULT_RATE,
|
|
2168
|
+
period: DEFAULT_PERIOD_SECONDS
|
|
2169
|
+
};
|
|
2170
|
+
for (const entry of (value ?? "").split(",")) {
|
|
2171
|
+
const [name, rawValue] = entry.split("=").map((part) => part.trim());
|
|
2172
|
+
if (!name || rawValue === void 0) {
|
|
2173
|
+
continue;
|
|
2174
|
+
}
|
|
2175
|
+
switch (name) {
|
|
2176
|
+
case "parallelism": {
|
|
2177
|
+
flowControl.parallelism = Number(rawValue);
|
|
2178
|
+
break;
|
|
2179
|
+
}
|
|
2180
|
+
case "rate": {
|
|
2181
|
+
flowControl.rate = Number(rawValue);
|
|
2182
|
+
break;
|
|
2183
|
+
}
|
|
2184
|
+
case "period": {
|
|
2185
|
+
flowControl.period = durationToSeconds(rawValue) ?? DEFAULT_PERIOD_SECONDS;
|
|
2186
|
+
break;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
return flowControl;
|
|
2191
|
+
};
|
|
2192
|
+
var getEffectiveConfig = (headers) => {
|
|
2193
|
+
const retriesHeader = headers.get(RETRIES_HEADER);
|
|
2194
|
+
return {
|
|
2195
|
+
flowControl: parseFlowControlHeaders(
|
|
2196
|
+
headers.get(FLOW_CONTROL_KEY_HEADER),
|
|
2197
|
+
headers.get(FLOW_CONTROL_VALUE_HEADER)
|
|
2198
|
+
),
|
|
2199
|
+
retries: retriesHeader === null ? void 0 : Number(retriesHeader),
|
|
2200
|
+
retryDelay: headers.get(RETRY_DELAY_HEADER) ?? void 0,
|
|
2201
|
+
hasStepConfig: headers.get(WORKFLOW_STEP_CONFIG_HEADER) === "true"
|
|
2202
|
+
};
|
|
2203
|
+
};
|
|
2204
|
+
var describeStepSettingsMismatch = (stepSettings, effectiveConfig) => {
|
|
2205
|
+
if (stepSettings.flowControl) {
|
|
2206
|
+
const wanted = normalizeFlowControl(stepSettings.flowControl);
|
|
2207
|
+
const applied = effectiveConfig.flowControl;
|
|
2208
|
+
if (!applied || applied.key !== wanted.key || applied.parallelism !== wanted.parallelism || applied.rate !== wanted.rate || applied.period !== wanted.period) {
|
|
2209
|
+
return `flow control: expected ${JSON.stringify(wanted)}, delivery has ${JSON.stringify(applied)}`;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
if (stepSettings.retries !== void 0 && effectiveConfig.retries !== void 0 && stepSettings.retries !== effectiveConfig.retries) {
|
|
2213
|
+
return `retries: expected ${stepSettings.retries}, delivery has ${effectiveConfig.retries}`;
|
|
2214
|
+
}
|
|
2215
|
+
if (stepSettings.retryDelay !== void 0 && stepSettings.retryDelay !== effectiveConfig.retryDelay) {
|
|
2216
|
+
return `retry delay: expected '${stepSettings.retryDelay}', delivery has '${effectiveConfig.retryDelay ?? ""}'`;
|
|
2217
|
+
}
|
|
2218
|
+
return void 0;
|
|
2219
|
+
};
|
|
1990
2220
|
|
|
1991
2221
|
// src/context/auto-executor.ts
|
|
1992
2222
|
var AutoExecutor = class _AutoExecutor {
|
|
@@ -2003,6 +2233,25 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2003
2233
|
stepCount = 0;
|
|
2004
2234
|
planStepCount = 0;
|
|
2005
2235
|
executingStep = false;
|
|
2236
|
+
/**
|
|
2237
|
+
* an executed step whose result hasn't been submitted to QStash yet.
|
|
2238
|
+
*
|
|
2239
|
+
* Set when a step which can produce its result in-process executes. The
|
|
2240
|
+
* submission waits so that the route function can continue and reveal
|
|
2241
|
+
* what comes next: if that is a single step with step-level settings,
|
|
2242
|
+
* the settings ride on this submission and no step config request is
|
|
2243
|
+
* needed, since the delivery of the submission is what executes it.
|
|
2244
|
+
*
|
|
2245
|
+
* Flushed when the next step is reached (see `addStep`) or when the
|
|
2246
|
+
* route function ends (see `flushPendingStep` in `serve`).
|
|
2247
|
+
*/
|
|
2248
|
+
pendingStep;
|
|
2249
|
+
/**
|
|
2250
|
+
* configuration QStash applied to the delivery being handled. A step
|
|
2251
|
+
* with step-level settings is settled by comparing its settings against
|
|
2252
|
+
* this.
|
|
2253
|
+
*/
|
|
2254
|
+
effectiveConfig;
|
|
2006
2255
|
/**
|
|
2007
2256
|
* @param context workflow context
|
|
2008
2257
|
* @param steps list of steps
|
|
@@ -2010,14 +2259,17 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2010
2259
|
* @param dispatchLifecycle lifecycle event dispatcher
|
|
2011
2260
|
* @param telemetry optional telemetry information
|
|
2012
2261
|
* @param invokeCount optional invoke count
|
|
2262
|
+
* @param effectiveConfig configuration QStash applied to the delivery
|
|
2263
|
+
* being handled
|
|
2013
2264
|
*/
|
|
2014
|
-
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount) {
|
|
2265
|
+
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount, effectiveConfig) {
|
|
2015
2266
|
this.context = context;
|
|
2016
2267
|
this.steps = steps;
|
|
2017
2268
|
this.dispatchDebug = dispatchDebug;
|
|
2018
2269
|
this.dispatchLifecycle = dispatchLifecycle;
|
|
2019
2270
|
this.telemetry = telemetry;
|
|
2020
2271
|
this.invokeCount = invokeCount ?? 0;
|
|
2272
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2021
2273
|
this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
|
|
2022
2274
|
}
|
|
2023
2275
|
/**
|
|
@@ -2052,6 +2304,15 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2052
2304
|
lazyStepList.push(stepInfo);
|
|
2053
2305
|
const index = this.indexInCurrentList++;
|
|
2054
2306
|
const requestComplete = this.deferExecution().then(async () => {
|
|
2307
|
+
const submitted = await this.submitPendingStep(
|
|
2308
|
+
lazyStepList.length === 1 ? lazyStepList[0].stepSettings : void 0
|
|
2309
|
+
);
|
|
2310
|
+
if (submitted.isErr()) {
|
|
2311
|
+
throw submitted.error;
|
|
2312
|
+
}
|
|
2313
|
+
if (submitted.value.result === "submitted-step") {
|
|
2314
|
+
throw submitted.value.abort;
|
|
2315
|
+
}
|
|
2055
2316
|
if (!this.promises.has(lazyStepList)) {
|
|
2056
2317
|
const promise2 = this.getExecutionPromise(lazyStepList);
|
|
2057
2318
|
this.promises.set(lazyStepList, promise2);
|
|
@@ -2105,18 +2366,100 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2105
2366
|
}
|
|
2106
2367
|
return parsedOut;
|
|
2107
2368
|
}
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2369
|
+
const mismatch = lazyStep.stepSettings ? describeStepSettingsMismatch(lazyStep.stepSettings, this.effectiveConfig) : void 0;
|
|
2370
|
+
if (mismatch) {
|
|
2371
|
+
if (!this.effectiveConfig.hasStepConfig) {
|
|
2372
|
+
await publishStepConfigRequest({
|
|
2373
|
+
context: this.context,
|
|
2374
|
+
lazyStep,
|
|
2375
|
+
targetStep: this.stepCount,
|
|
2376
|
+
invokeCount: this.invokeCount,
|
|
2377
|
+
telemetry: this.telemetry,
|
|
2378
|
+
dispatchDebug: this.dispatchDebug
|
|
2379
|
+
});
|
|
2380
|
+
throw new WorkflowAbort(lazyStep.stepName);
|
|
2381
|
+
} else {
|
|
2382
|
+
await this.dispatchDebug("onWarning", {
|
|
2383
|
+
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.`
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
const resultStep = await executeStep({
|
|
2110
2388
|
lazyStep,
|
|
2111
2389
|
stepId: this.stepCount,
|
|
2390
|
+
concurrency: NO_CONCURRENCY,
|
|
2391
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2392
|
+
});
|
|
2393
|
+
if (lazyStep.supportsDeferredSubmission) {
|
|
2394
|
+
this.pendingStep = { status: "held", lazyStep, resultStep };
|
|
2395
|
+
return lazyStep.parseOut({
|
|
2396
|
+
...resultStep,
|
|
2397
|
+
out: resultStep.out === void 0 ? void 0 : JSON.stringify(resultStep.out)
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
await submitStepResult({
|
|
2401
|
+
context: this.context,
|
|
2402
|
+
lazyStep,
|
|
2403
|
+
resultStep,
|
|
2112
2404
|
invokeCount: this.invokeCount,
|
|
2113
|
-
concurrency:
|
|
2405
|
+
concurrency: NO_CONCURRENCY,
|
|
2114
2406
|
telemetry: this.telemetry,
|
|
2115
|
-
dispatchDebug: this.dispatchDebug
|
|
2116
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2407
|
+
dispatchDebug: this.dispatchDebug
|
|
2117
2408
|
});
|
|
2118
2409
|
throw new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2119
2410
|
}
|
|
2411
|
+
/**
|
|
2412
|
+
* Submits the result of the step being held, if there is one, and
|
|
2413
|
+
* returns the abort the caller has to throw to end the invocation.
|
|
2414
|
+
*
|
|
2415
|
+
* Returns undefined when no step is being held, which is the caller's
|
|
2416
|
+
* signal that there is nothing to end. Throws instead of returning
|
|
2417
|
+
* when the submission itself fails, so that error reaches the caller
|
|
2418
|
+
* rather than an abort claiming the step was submitted.
|
|
2419
|
+
*
|
|
2420
|
+
* Callers which arrive while a submission is already under way (the
|
|
2421
|
+
* steps of a parallel group, added together) wait for that one and get
|
|
2422
|
+
* the same abort, rather than submitting a second time.
|
|
2423
|
+
*
|
|
2424
|
+
* The next step's settings are attached whenever it has any, without
|
|
2425
|
+
* checking them against the current delivery: the executor only knows
|
|
2426
|
+
* the configuration of the delivery in hand, which inside a
|
|
2427
|
+
* delivery is the *previous* step's, so such a check would be wrong
|
|
2428
|
+
* exactly when two steps with settings follow each other.
|
|
2429
|
+
*
|
|
2430
|
+
* @param nextStepSettings step-level settings of the next step
|
|
2431
|
+
* @returns `submitted-step` with the abort which ends this invocation,
|
|
2432
|
+
* or `no-pending-step` when nothing was held and there is nothing to
|
|
2433
|
+
* end
|
|
2434
|
+
*/
|
|
2435
|
+
async submitPendingStep(nextStepSettings) {
|
|
2436
|
+
if (!this.pendingStep) {
|
|
2437
|
+
return ok({ result: "no-pending-step" });
|
|
2438
|
+
}
|
|
2439
|
+
try {
|
|
2440
|
+
if (this.pendingStep.status === "submitting") {
|
|
2441
|
+
return ok({ result: "submitted-step", abort: await this.pendingStep.submitted });
|
|
2442
|
+
}
|
|
2443
|
+
const { lazyStep, resultStep } = this.pendingStep;
|
|
2444
|
+
const submitted = (async () => {
|
|
2445
|
+
await submitStepResult({
|
|
2446
|
+
context: this.context,
|
|
2447
|
+
lazyStep,
|
|
2448
|
+
resultStep,
|
|
2449
|
+
invokeCount: this.invokeCount,
|
|
2450
|
+
concurrency: NO_CONCURRENCY,
|
|
2451
|
+
telemetry: this.telemetry,
|
|
2452
|
+
dispatchDebug: this.dispatchDebug,
|
|
2453
|
+
nextStepSettings
|
|
2454
|
+
});
|
|
2455
|
+
return new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2456
|
+
})();
|
|
2457
|
+
this.pendingStep = { status: "submitting", submitted };
|
|
2458
|
+
return ok({ result: "submitted-step", abort: await submitted });
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
return err(error);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2120
2463
|
/**
|
|
2121
2464
|
* Runs steps in parallel.
|
|
2122
2465
|
*
|
|
@@ -2165,15 +2508,20 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2165
2508
|
validateStep(parallelSteps[stepIndex], planStep);
|
|
2166
2509
|
try {
|
|
2167
2510
|
const parallelStep = parallelSteps[stepIndex];
|
|
2168
|
-
const resultStep = await
|
|
2169
|
-
context: this.context,
|
|
2511
|
+
const resultStep = await executeStep({
|
|
2170
2512
|
lazyStep: parallelStep,
|
|
2171
2513
|
stepId: planStep.targetStep,
|
|
2514
|
+
concurrency: parallelSteps.length,
|
|
2515
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2516
|
+
});
|
|
2517
|
+
await submitStepResult({
|
|
2518
|
+
context: this.context,
|
|
2519
|
+
lazyStep: parallelStep,
|
|
2520
|
+
resultStep,
|
|
2172
2521
|
invokeCount: this.invokeCount,
|
|
2173
2522
|
concurrency: parallelSteps.length,
|
|
2174
2523
|
telemetry: this.telemetry,
|
|
2175
|
-
dispatchDebug: this.dispatchDebug
|
|
2176
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2524
|
+
dispatchDebug: this.dispatchDebug
|
|
2177
2525
|
});
|
|
2178
2526
|
throw new WorkflowAbort(parallelStep.stepName, resultStep);
|
|
2179
2527
|
} catch (error) {
|
|
@@ -2789,6 +3137,46 @@ var WorkflowContext = class {
|
|
|
2789
3137
|
* `1` on the first retry, `2` on the second, and so on.
|
|
2790
3138
|
*/
|
|
2791
3139
|
retried;
|
|
3140
|
+
/**
|
|
3141
|
+
* Configuration QStash applied to the request being handled.
|
|
3142
|
+
*
|
|
3143
|
+
* This is the configuration of *this delivery*, not of the run: it is
|
|
3144
|
+
* the configuration the run was triggered with, except inside the
|
|
3145
|
+
* delivery which executes a step that has step-level settings, where
|
|
3146
|
+
* it is that step's settings. The fields below expose it; the executor
|
|
3147
|
+
* gets it directly rather than reaching through the context.
|
|
3148
|
+
*/
|
|
3149
|
+
effectiveConfig;
|
|
3150
|
+
/**
|
|
3151
|
+
* Flow control QStash applied to the request being handled, if any.
|
|
3152
|
+
*
|
|
3153
|
+
* This is the flow control of the request in hand, which is the one the
|
|
3154
|
+
* run was triggered with except inside a step carrying its own.
|
|
3155
|
+
*/
|
|
3156
|
+
get flowControl() {
|
|
3157
|
+
return this.effectiveConfig.flowControl;
|
|
3158
|
+
}
|
|
3159
|
+
/**
|
|
3160
|
+
* Retry limit QStash applied to the request being handled, if it
|
|
3161
|
+
* reported one.
|
|
3162
|
+
*
|
|
3163
|
+
* Not to be confused with {@link retried}, which is how many retries
|
|
3164
|
+
* have already happened.
|
|
3165
|
+
*
|
|
3166
|
+
* @see {@link flowControl} for what "the request being handled" means
|
|
3167
|
+
*/
|
|
3168
|
+
get retries() {
|
|
3169
|
+
return this.effectiveConfig.retries;
|
|
3170
|
+
}
|
|
3171
|
+
/**
|
|
3172
|
+
* Retry delay expression QStash applied to the request being handled,
|
|
3173
|
+
* if any.
|
|
3174
|
+
*
|
|
3175
|
+
* @see {@link flowControl} for what "the request being handled" means
|
|
3176
|
+
*/
|
|
3177
|
+
get retryDelay() {
|
|
3178
|
+
return this.effectiveConfig.retryDelay;
|
|
3179
|
+
}
|
|
2792
3180
|
constructor({
|
|
2793
3181
|
qstashClient,
|
|
2794
3182
|
workflowRunId,
|
|
@@ -2802,7 +3190,8 @@ var WorkflowContext = class {
|
|
|
2802
3190
|
invokeCount,
|
|
2803
3191
|
label,
|
|
2804
3192
|
retried,
|
|
2805
|
-
middlewareManager
|
|
3193
|
+
middlewareManager,
|
|
3194
|
+
effectiveConfig
|
|
2806
3195
|
}) {
|
|
2807
3196
|
this.qstashClient = qstashClient;
|
|
2808
3197
|
this.workflowRunId = workflowRunId;
|
|
@@ -2814,6 +3203,7 @@ var WorkflowContext = class {
|
|
|
2814
3203
|
this.env = env ?? {};
|
|
2815
3204
|
this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
|
|
2816
3205
|
this.retried = retried ?? 0;
|
|
3206
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2817
3207
|
const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
|
|
2818
3208
|
middlewareManagerInstance.assignContext(this);
|
|
2819
3209
|
this.executor = new AutoExecutor(
|
|
@@ -2822,7 +3212,8 @@ var WorkflowContext = class {
|
|
|
2822
3212
|
middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
|
|
2823
3213
|
middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
|
|
2824
3214
|
telemetry,
|
|
2825
|
-
invokeCount
|
|
3215
|
+
invokeCount,
|
|
3216
|
+
this.effectiveConfig
|
|
2826
3217
|
);
|
|
2827
3218
|
}
|
|
2828
3219
|
/**
|
|
@@ -2848,13 +3239,35 @@ var WorkflowContext = class {
|
|
|
2848
3239
|
* ])
|
|
2849
3240
|
* ```
|
|
2850
3241
|
*
|
|
3242
|
+
* Step-level settings can be passed as a third argument, overriding
|
|
3243
|
+
* the settings the workflow run was triggered with for this step only:
|
|
3244
|
+
*
|
|
3245
|
+
* ```typescript
|
|
3246
|
+
* const result = await context.run(
|
|
3247
|
+
* "step 1",
|
|
3248
|
+
* () => {
|
|
3249
|
+
* return "result"
|
|
3250
|
+
* },
|
|
3251
|
+
* {
|
|
3252
|
+
* flowControl: { key: "custom-key", parallelism: 3 },
|
|
3253
|
+
* retries: 5,
|
|
3254
|
+
* }
|
|
3255
|
+
* )
|
|
3256
|
+
* ```
|
|
3257
|
+
*
|
|
2851
3258
|
* @param stepName name of the step
|
|
2852
3259
|
* @param stepFunction step function to be executed
|
|
3260
|
+
* @param stepSettings step-level settings for this step
|
|
2853
3261
|
* @returns result of the step function
|
|
2854
3262
|
*/
|
|
2855
|
-
async run(stepName, stepFunction) {
|
|
3263
|
+
async run(stepName, stepFunction, stepSettings) {
|
|
3264
|
+
if (stepSettings) {
|
|
3265
|
+
validateFlowControl(stepSettings.flowControl);
|
|
3266
|
+
}
|
|
2856
3267
|
const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
|
|
2857
|
-
return await this.addStep(
|
|
3268
|
+
return await this.addStep(
|
|
3269
|
+
new LazyFunctionStep(this, stepName, wrappedStepFunction, stepSettings)
|
|
3270
|
+
);
|
|
2858
3271
|
}
|
|
2859
3272
|
/**
|
|
2860
3273
|
* Stops the execution for the duration provided.
|
|
@@ -3077,7 +3490,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3077
3490
|
*
|
|
3078
3491
|
* @param routeFunction
|
|
3079
3492
|
*/
|
|
3080
|
-
static async tryAuthentication(routeFunction, context) {
|
|
3493
|
+
static async tryAuthentication(routeFunction, context, effectiveConfig) {
|
|
3081
3494
|
const disabledContext = new _DisabledWorkflowContext({
|
|
3082
3495
|
qstashClient: new import_qstash10.Client({
|
|
3083
3496
|
baseUrl: "disabled-client",
|
|
@@ -3092,7 +3505,10 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3092
3505
|
initialPayload: context.requestPayload,
|
|
3093
3506
|
env: context.env,
|
|
3094
3507
|
label: context.labels,
|
|
3095
|
-
retried: context.retried
|
|
3508
|
+
retried: context.retried,
|
|
3509
|
+
// the route function runs for real here until it reaches a step, so
|
|
3510
|
+
// it observes the same configuration as it will on the real context
|
|
3511
|
+
effectiveConfig
|
|
3096
3512
|
});
|
|
3097
3513
|
try {
|
|
3098
3514
|
await routeFunction(disabledContext);
|
|
@@ -3309,6 +3725,7 @@ var handleFailure = async ({
|
|
|
3309
3725
|
}
|
|
3310
3726
|
const userHeaders = recreateUserHeaders(request.headers);
|
|
3311
3727
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3728
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3312
3729
|
const workflowContext = new WorkflowContext({
|
|
3313
3730
|
qstashClient,
|
|
3314
3731
|
workflowRunId,
|
|
@@ -3322,11 +3739,13 @@ var handleFailure = async ({
|
|
|
3322
3739
|
label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
|
|
3323
3740
|
retried,
|
|
3324
3741
|
workflowRunCreatedAt: workflowCreatedAt,
|
|
3325
|
-
middlewareManager: void 0
|
|
3742
|
+
middlewareManager: void 0,
|
|
3743
|
+
effectiveConfig
|
|
3326
3744
|
});
|
|
3327
3745
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3328
3746
|
routeFunction,
|
|
3329
|
-
workflowContext
|
|
3747
|
+
workflowContext,
|
|
3748
|
+
effectiveConfig
|
|
3330
3749
|
);
|
|
3331
3750
|
if (authCheck.isErr()) {
|
|
3332
3751
|
await dispatchDebug?.("onError", {
|
|
@@ -3418,7 +3837,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
|
|
|
3418
3837
|
const client = new import_qstash11.Client({
|
|
3419
3838
|
...clientOptions,
|
|
3420
3839
|
baseUrl: clientEnv.QSTASH_URL,
|
|
3421
|
-
token: clientEnv.QSTASH_TOKEN
|
|
3840
|
+
token: clientEnv.QSTASH_TOKEN,
|
|
3841
|
+
// Mirror the receiver's dev-mode decision so a single QSTASH_DEV=true points
|
|
3842
|
+
// the client at the local dev server too. Without this the QStash Client
|
|
3843
|
+
// re-derives dev mode from its own process.env, which is blind to the
|
|
3844
|
+
// Cloudflare worker `env` binding that actually carries QSTASH_DEV there.
|
|
3845
|
+
// An explicit devMode in the user's client config still wins.
|
|
3846
|
+
devMode: clientOptions?.devMode ?? isQStashDevModeEnabled(environment)
|
|
3422
3847
|
});
|
|
3423
3848
|
const receiver = getReceiver(environment, receiverConfig, region);
|
|
3424
3849
|
return { client, receiver };
|
|
@@ -3468,7 +3893,9 @@ var getQStashHandlers = ({
|
|
|
3468
3893
|
client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
|
|
3469
3894
|
...qstashClientOption,
|
|
3470
3895
|
baseUrl: environment.QSTASH_URL,
|
|
3471
|
-
token: environment.QSTASH_TOKEN
|
|
3896
|
+
token: environment.QSTASH_TOKEN,
|
|
3897
|
+
// Mirror the receiver's dev-mode decision (see createRegionalHandler).
|
|
3898
|
+
devMode: qstashClientOption?.devMode ?? isQStashDevModeEnabled(environment)
|
|
3472
3899
|
}),
|
|
3473
3900
|
receiver: getReceiver(environment, receiverConfig)
|
|
3474
3901
|
}
|
|
@@ -3808,12 +4235,14 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3808
4235
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3809
4236
|
const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
|
|
3810
4237
|
const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
4238
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3811
4239
|
const workflowContext = new WorkflowContext({
|
|
3812
4240
|
qstashClient: regionalClient,
|
|
3813
4241
|
workflowRunId,
|
|
3814
4242
|
initialPayload: isThirdPartyCallResult(request) ? JSON.parse(rawInitialPayload) : initialPayloadParser(rawInitialPayload),
|
|
3815
4243
|
headers: recreateUserHeaders(request.headers),
|
|
3816
4244
|
steps,
|
|
4245
|
+
effectiveConfig,
|
|
3817
4246
|
url: workflowUrl,
|
|
3818
4247
|
env,
|
|
3819
4248
|
telemetry,
|
|
@@ -3825,7 +4254,8 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3825
4254
|
});
|
|
3826
4255
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3827
4256
|
routeFunction,
|
|
3828
|
-
workflowContext
|
|
4257
|
+
workflowContext,
|
|
4258
|
+
effectiveConfig
|
|
3829
4259
|
);
|
|
3830
4260
|
if (authCheck.isErr()) {
|
|
3831
4261
|
throw authCheck.error;
|
|
@@ -3862,7 +4292,23 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3862
4292
|
if (steps.length === 1) {
|
|
3863
4293
|
await middlewareManager.dispatchLifecycle("runStarted", {});
|
|
3864
4294
|
}
|
|
3865
|
-
|
|
4295
|
+
let outcome;
|
|
4296
|
+
try {
|
|
4297
|
+
outcome = { ran: true, result: await routeFunction(workflowContext) };
|
|
4298
|
+
} catch (error) {
|
|
4299
|
+
outcome = { ran: false, error };
|
|
4300
|
+
}
|
|
4301
|
+
const submitted = await flushPendingStep(workflowContext);
|
|
4302
|
+
if (submitted.isErr()) {
|
|
4303
|
+
throw submitted.error;
|
|
4304
|
+
}
|
|
4305
|
+
if (submitted.value.result === "submitted-step") {
|
|
4306
|
+
throw submitted.value.abort;
|
|
4307
|
+
}
|
|
4308
|
+
if (!outcome.ran) {
|
|
4309
|
+
throw outcome.error;
|
|
4310
|
+
}
|
|
4311
|
+
return outcome.result;
|
|
3866
4312
|
},
|
|
3867
4313
|
onCleanup: async (result2) => {
|
|
3868
4314
|
await middlewareManager.dispatchLifecycle("runCompleted", {
|
|
@@ -3943,6 +4389,7 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3943
4389
|
};
|
|
3944
4390
|
|
|
3945
4391
|
// platforms/nextjs.ts
|
|
4392
|
+
var import_qstash13 = require("@upstash/qstash");
|
|
3946
4393
|
var appTelemetry = {
|
|
3947
4394
|
sdk: SDK_TELEMETRY,
|
|
3948
4395
|
framework: "nextjs",
|
|
@@ -3954,6 +4401,7 @@ var pagesTelemetry = {
|
|
|
3954
4401
|
runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
|
|
3955
4402
|
};
|
|
3956
4403
|
var serve = (routeFunction, options) => {
|
|
4404
|
+
void (0, import_qstash13.startDevServer)();
|
|
3957
4405
|
const { handler: serveHandler } = serveBase(
|
|
3958
4406
|
routeFunction,
|
|
3959
4407
|
appTelemetry,
|
|
@@ -3986,6 +4434,7 @@ var serveMany = (workflows, options) => {
|
|
|
3986
4434
|
};
|
|
3987
4435
|
};
|
|
3988
4436
|
var servePagesRouter = (routeFunction, options) => {
|
|
4437
|
+
void (0, import_qstash13.startDevServer)();
|
|
3989
4438
|
const { handler: serveHandler } = serveBase(routeFunction, pagesTelemetry, options, void 0);
|
|
3990
4439
|
const handler = async (request_, res) => {
|
|
3991
4440
|
if (request_.method?.toUpperCase() !== "POST") {
|