@upstash/workflow 1.3.2 → 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 +595 -140
- package/astro.mjs +3 -1
- package/{chunk-ICDNN4UW.mjs → chunk-UH6O5IJY.mjs} +751 -281
- package/cloudflare.d.mts +3 -2
- package/cloudflare.d.ts +3 -2
- package/cloudflare.js +595 -140
- package/cloudflare.mjs +3 -1
- package/express.d.mts +3 -2
- package/express.d.ts +3 -2
- package/express.js +596 -140
- package/express.mjs +4 -1
- package/h3.d.mts +3 -2
- package/h3.d.ts +3 -2
- package/h3.js +595 -140
- package/h3.mjs +3 -1
- package/hono.d.mts +3 -2
- package/hono.d.ts +3 -2
- package/hono.js +595 -140
- package/hono.mjs +3 -1
- package/index.d.mts +64 -17
- package/index.d.ts +64 -17
- package/index.js +652 -150
- package/index.mjs +49 -11
- package/nextjs.d.mts +3 -2
- package/nextjs.d.ts +3 -2
- package/nextjs.js +596 -140
- 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 +595 -140
- 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 +595 -140
- package/solidjs.mjs +3 -1
- package/svelte.d.mts +3 -2
- package/svelte.d.ts +3 -2
- package/svelte.js +595 -140
- package/svelte.mjs +3 -1
- package/tanstack.d.mts +3 -2
- package/tanstack.d.ts +3 -2
- package/tanstack.js +595 -140
- 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
|
|
|
@@ -145,8 +148,15 @@ function isInstanceOf(v, ctor) {
|
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
// src/client/utils.ts
|
|
151
|
+
var assertNonEmptyId = (id, label = "id") => {
|
|
152
|
+
if (!id) {
|
|
153
|
+
throw new import_qstash2.QstashError(`${label} cannot be empty`);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
148
156
|
var makeNotifyRequest = async (requester, eventId, eventData, workflowRunId) => {
|
|
149
|
-
|
|
157
|
+
assertNonEmptyId(eventId, "Event id");
|
|
158
|
+
if (workflowRunId !== void 0) assertNonEmptyId(workflowRunId, "Workflow run id");
|
|
159
|
+
const path = workflowRunId === void 0 ? ["v2", "notify", eventId] : ["v2", "notify", workflowRunId, eventId];
|
|
150
160
|
const result = await requester.request({
|
|
151
161
|
path,
|
|
152
162
|
method: "POST",
|
|
@@ -210,6 +220,14 @@ var WORKFLOW_CREATED_AT_HEADER = "Upstash-Workflow-CreatedAt";
|
|
|
210
220
|
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
|
|
211
221
|
var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
|
|
212
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";
|
|
213
231
|
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
|
|
214
232
|
var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
|
|
215
233
|
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
|
|
@@ -227,102 +245,6 @@ var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
|
|
|
227
245
|
var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
|
|
228
246
|
var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
|
|
229
247
|
|
|
230
|
-
// src/context/auto-executor.ts
|
|
231
|
-
var import_qstash5 = require("@upstash/qstash");
|
|
232
|
-
|
|
233
|
-
// src/qstash/headers.ts
|
|
234
|
-
var import_qstash4 = require("@upstash/qstash");
|
|
235
|
-
|
|
236
|
-
// src/utils.ts
|
|
237
|
-
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
238
|
-
var NANOID_LENGTH = 21;
|
|
239
|
-
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
240
|
-
function validateLabel(label) {
|
|
241
|
-
if (label === void 0) return;
|
|
242
|
-
const labels = Array.isArray(label) ? label : [label];
|
|
243
|
-
if (labels.length === 0) {
|
|
244
|
-
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
245
|
-
}
|
|
246
|
-
for (const value of labels) {
|
|
247
|
-
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
248
|
-
throw new WorkflowNonRetryableError(
|
|
249
|
-
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
function serializeLabel(label) {
|
|
255
|
-
return Array.isArray(label) ? label.join(",") : label;
|
|
256
|
-
}
|
|
257
|
-
function validateFlowControl(flowControl) {
|
|
258
|
-
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
259
|
-
throw new WorkflowNonRetryableError(
|
|
260
|
-
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
function getRandomInt() {
|
|
265
|
-
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
266
|
-
}
|
|
267
|
-
function nanoid(length = NANOID_LENGTH) {
|
|
268
|
-
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
269
|
-
}
|
|
270
|
-
function getWorkflowRunId(id) {
|
|
271
|
-
return `wfr_${id ?? nanoid()}`;
|
|
272
|
-
}
|
|
273
|
-
function decodeBase64(base64) {
|
|
274
|
-
const binString = atob(base64);
|
|
275
|
-
try {
|
|
276
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
277
|
-
return new TextDecoder().decode(intArray);
|
|
278
|
-
} catch (error) {
|
|
279
|
-
console.warn(
|
|
280
|
-
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
281
|
-
);
|
|
282
|
-
return binString;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
function getUserIdFromToken(qstashClient) {
|
|
286
|
-
try {
|
|
287
|
-
const token = qstashClient.token;
|
|
288
|
-
const decodedToken = decodeBase64(token);
|
|
289
|
-
const tokenPayload = JSON.parse(decodedToken);
|
|
290
|
-
const userId = tokenPayload.UserID;
|
|
291
|
-
if (!userId) {
|
|
292
|
-
throw new WorkflowError("QStash token payload does not contain userId");
|
|
293
|
-
}
|
|
294
|
-
return userId;
|
|
295
|
-
} catch (error) {
|
|
296
|
-
throw new WorkflowError(
|
|
297
|
-
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
function getQStashUrl(qstashClient) {
|
|
302
|
-
try {
|
|
303
|
-
const requester = qstashClient.http;
|
|
304
|
-
const baseUrl = requester.baseUrl;
|
|
305
|
-
if (!baseUrl) {
|
|
306
|
-
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
307
|
-
}
|
|
308
|
-
return baseUrl;
|
|
309
|
-
} catch (error) {
|
|
310
|
-
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
function getEventId() {
|
|
314
|
-
return `evt_${nanoid(15)}`;
|
|
315
|
-
}
|
|
316
|
-
function stringifyBody(body) {
|
|
317
|
-
if (body === void 0) {
|
|
318
|
-
return void 0;
|
|
319
|
-
}
|
|
320
|
-
if (typeof body === "string") {
|
|
321
|
-
return body;
|
|
322
|
-
}
|
|
323
|
-
return JSON.stringify(body);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
248
|
// node_modules/neverthrow/dist/index.es.js
|
|
327
249
|
var defaultErrorConfig = {
|
|
328
250
|
withStackTrace: false
|
|
@@ -738,6 +660,102 @@ var Err = class {
|
|
|
738
660
|
};
|
|
739
661
|
var fromThrowable = Result.fromThrowable;
|
|
740
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
|
+
|
|
741
759
|
// src/types.ts
|
|
742
760
|
var StepTypes = [
|
|
743
761
|
"Initial",
|
|
@@ -843,6 +861,10 @@ var triggerFirstInvocation = async (params) => {
|
|
|
843
861
|
return err(error_);
|
|
844
862
|
}
|
|
845
863
|
};
|
|
864
|
+
var flushPendingStep = async (workflowContext) => {
|
|
865
|
+
const { executor } = workflowContext;
|
|
866
|
+
return await executor.submitPendingStep();
|
|
867
|
+
};
|
|
846
868
|
var triggerRouteFunction = async ({
|
|
847
869
|
onCleanup,
|
|
848
870
|
onStep,
|
|
@@ -1050,8 +1072,29 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
|
|
|
1050
1072
|
var BaseLazyStep = class _BaseLazyStep {
|
|
1051
1073
|
stepName;
|
|
1052
1074
|
context;
|
|
1053
|
-
|
|
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) {
|
|
1054
1096
|
this.context = context;
|
|
1097
|
+
this.stepSettings = stepSettings;
|
|
1055
1098
|
if (!stepName) {
|
|
1056
1099
|
throw new WorkflowError(
|
|
1057
1100
|
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
|
|
@@ -1115,7 +1158,13 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1115
1158
|
step.out = JSON.stringify(step.out);
|
|
1116
1159
|
return JSON.stringify(step);
|
|
1117
1160
|
}
|
|
1118
|
-
getHeaders({
|
|
1161
|
+
getHeaders({
|
|
1162
|
+
context,
|
|
1163
|
+
telemetry,
|
|
1164
|
+
invokeCount,
|
|
1165
|
+
step,
|
|
1166
|
+
stepSettings
|
|
1167
|
+
}) {
|
|
1119
1168
|
return getHeaders({
|
|
1120
1169
|
initHeaderValue: "false",
|
|
1121
1170
|
workflowConfig: {
|
|
@@ -1128,7 +1177,8 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1128
1177
|
stepInfo: {
|
|
1129
1178
|
step,
|
|
1130
1179
|
lazyStep: this
|
|
1131
|
-
}
|
|
1180
|
+
},
|
|
1181
|
+
stepSettings
|
|
1132
1182
|
});
|
|
1133
1183
|
}
|
|
1134
1184
|
async submitStep({ context, body, headers }) {
|
|
@@ -1146,8 +1196,9 @@ var LazyFunctionStep = class extends BaseLazyStep {
|
|
|
1146
1196
|
stepFunction;
|
|
1147
1197
|
stepType = "Run";
|
|
1148
1198
|
allowUndefinedOut = true;
|
|
1149
|
-
|
|
1150
|
-
|
|
1199
|
+
supportsDeferredSubmission = true;
|
|
1200
|
+
constructor(context, stepName, stepFunction, stepSettings) {
|
|
1201
|
+
super(context, stepName, stepSettings);
|
|
1151
1202
|
this.stepFunction = stepFunction;
|
|
1152
1203
|
}
|
|
1153
1204
|
getPlanStep(concurrent, targetStep) {
|
|
@@ -1177,6 +1228,7 @@ var LazySleepStep = class extends BaseLazyStep {
|
|
|
1177
1228
|
sleep;
|
|
1178
1229
|
stepType = "SleepFor";
|
|
1179
1230
|
allowUndefinedOut = true;
|
|
1231
|
+
supportsDeferredSubmission = true;
|
|
1180
1232
|
constructor(context, stepName, sleep) {
|
|
1181
1233
|
super(context, stepName);
|
|
1182
1234
|
this.sleep = sleep;
|
|
@@ -1216,6 +1268,7 @@ var LazySleepUntilStep = class extends BaseLazyStep {
|
|
|
1216
1268
|
sleepUntil;
|
|
1217
1269
|
stepType = "SleepUntil";
|
|
1218
1270
|
allowUndefinedOut = true;
|
|
1271
|
+
supportsDeferredSubmission = true;
|
|
1219
1272
|
constructor(context, stepName, sleepUntil) {
|
|
1220
1273
|
super(context, stepName);
|
|
1221
1274
|
this.sleepUntil = sleepUntil;
|
|
@@ -1708,6 +1761,7 @@ var WorkflowHeaders = class {
|
|
|
1708
1761
|
invokeCount;
|
|
1709
1762
|
initHeaderValue;
|
|
1710
1763
|
stepInfo;
|
|
1764
|
+
stepSettings;
|
|
1711
1765
|
headers;
|
|
1712
1766
|
/**
|
|
1713
1767
|
* @param params workflow header parameters
|
|
@@ -1717,17 +1771,20 @@ var WorkflowHeaders = class {
|
|
|
1717
1771
|
workflowConfig,
|
|
1718
1772
|
invokeCount,
|
|
1719
1773
|
initHeaderValue,
|
|
1720
|
-
stepInfo
|
|
1774
|
+
stepInfo,
|
|
1775
|
+
stepSettings
|
|
1721
1776
|
}) {
|
|
1722
1777
|
this.userHeaders = userHeaders;
|
|
1723
1778
|
this.workflowConfig = workflowConfig;
|
|
1724
1779
|
this.invokeCount = invokeCount;
|
|
1725
1780
|
this.initHeaderValue = initHeaderValue;
|
|
1726
1781
|
this.stepInfo = stepInfo;
|
|
1782
|
+
this.stepSettings = stepSettings;
|
|
1727
1783
|
this.headers = {
|
|
1728
1784
|
rawHeaders: {},
|
|
1729
1785
|
workflowHeaders: {},
|
|
1730
|
-
failureHeaders: {}
|
|
1786
|
+
failureHeaders: {},
|
|
1787
|
+
stepSettingsHeaders: {}
|
|
1731
1788
|
};
|
|
1732
1789
|
}
|
|
1733
1790
|
getHeaders() {
|
|
@@ -1738,6 +1795,7 @@ var WorkflowHeaders = class {
|
|
|
1738
1795
|
this.addUserHeaders();
|
|
1739
1796
|
this.addInvokeCount();
|
|
1740
1797
|
this.addFailureUrl();
|
|
1798
|
+
this.addStepSettings();
|
|
1741
1799
|
const contentType = this.addContentType();
|
|
1742
1800
|
return this.prefixHeaders(contentType);
|
|
1743
1801
|
}
|
|
@@ -1747,7 +1805,7 @@ var WorkflowHeaders = class {
|
|
|
1747
1805
|
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
|
|
1748
1806
|
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
|
|
1749
1807
|
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
|
|
1750
|
-
[WORKFLOW_FEATURE_HEADER]:
|
|
1808
|
+
[WORKFLOW_FEATURE_HEADER]: WORKFLOW_FEATURE_SET,
|
|
1751
1809
|
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
1752
1810
|
...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
|
|
1753
1811
|
};
|
|
@@ -1825,7 +1883,7 @@ var WorkflowHeaders = class {
|
|
|
1825
1883
|
this.headers.failureHeaders["Workflow-Init"] = "false";
|
|
1826
1884
|
this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
|
|
1827
1885
|
this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
|
|
1828
|
-
this.headers.failureHeaders["Feature-Set"] =
|
|
1886
|
+
this.headers.failureHeaders["Feature-Set"] = WORKFLOW_FEATURE_SET;
|
|
1829
1887
|
if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
|
|
1830
1888
|
this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
|
|
1831
1889
|
}
|
|
@@ -1833,6 +1891,42 @@ var WorkflowHeaders = class {
|
|
|
1833
1891
|
this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
|
|
1834
1892
|
}
|
|
1835
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
|
+
}
|
|
1836
1930
|
addContentType() {
|
|
1837
1931
|
if (this.workflowConfig.useJSONContent) {
|
|
1838
1932
|
this.headers.rawHeaders["content-type"] = "application/json";
|
|
@@ -1846,14 +1940,17 @@ var WorkflowHeaders = class {
|
|
|
1846
1940
|
return contentType;
|
|
1847
1941
|
}
|
|
1848
1942
|
prefixHeaders(contentType) {
|
|
1849
|
-
const { rawHeaders, workflowHeaders, failureHeaders } = this.headers;
|
|
1943
|
+
const { rawHeaders, workflowHeaders, failureHeaders, stepSettingsHeaders } = this.headers;
|
|
1850
1944
|
const isCall = this.stepInfo?.lazyStep.stepType === "Call";
|
|
1851
1945
|
return {
|
|
1852
1946
|
headers: {
|
|
1853
1947
|
...rawHeaders,
|
|
1854
1948
|
...addPrefixToHeaders(workflowHeaders, isCall ? "Upstash-Callback-" : "Upstash-"),
|
|
1855
1949
|
...addPrefixToHeaders(failureHeaders, "Upstash-Failure-Callback-"),
|
|
1856
|
-
...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
|
|
1857
1954
|
},
|
|
1858
1955
|
contentType
|
|
1859
1956
|
};
|
|
@@ -1904,7 +2001,7 @@ var submitParallelSteps = async ({
|
|
|
1904
2001
|
info: `Submitting ${planSteps.length} parallel steps.`
|
|
1905
2002
|
});
|
|
1906
2003
|
const result = await context.qstashClient.batch(
|
|
1907
|
-
planSteps.map((planStep) => {
|
|
2004
|
+
planSteps.map((planStep, index) => {
|
|
1908
2005
|
const { headers } = getHeaders({
|
|
1909
2006
|
initHeaderValue: "false",
|
|
1910
2007
|
workflowConfig: {
|
|
@@ -1912,7 +2009,8 @@ var submitParallelSteps = async ({
|
|
|
1912
2009
|
workflowUrl: context.url,
|
|
1913
2010
|
telemetry
|
|
1914
2011
|
},
|
|
1915
|
-
invokeCount
|
|
2012
|
+
invokeCount,
|
|
2013
|
+
stepSettings: steps[index].stepSettings
|
|
1916
2014
|
});
|
|
1917
2015
|
return {
|
|
1918
2016
|
headers,
|
|
@@ -1931,31 +2029,38 @@ var submitParallelSteps = async ({
|
|
|
1931
2029
|
}
|
|
1932
2030
|
throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
|
|
1933
2031
|
};
|
|
1934
|
-
var
|
|
1935
|
-
context,
|
|
2032
|
+
var executeStep = async ({
|
|
1936
2033
|
lazyStep,
|
|
1937
2034
|
stepId,
|
|
1938
|
-
invokeCount,
|
|
1939
2035
|
concurrency,
|
|
1940
|
-
telemetry,
|
|
1941
|
-
dispatchDebug,
|
|
1942
2036
|
dispatchLifecycle
|
|
1943
2037
|
}) => {
|
|
1944
2038
|
await dispatchLifecycle("beforeExecution", {
|
|
1945
2039
|
stepName: lazyStep.stepName
|
|
1946
2040
|
});
|
|
1947
|
-
let resultStep;
|
|
1948
2041
|
try {
|
|
1949
|
-
|
|
2042
|
+
return await lazyStep.getResultStep(concurrency, stepId);
|
|
1950
2043
|
} catch (error) {
|
|
1951
2044
|
attachStepNameToError(error, lazyStep.stepName);
|
|
1952
2045
|
throw error;
|
|
1953
2046
|
}
|
|
2047
|
+
};
|
|
2048
|
+
var submitStepResult = async ({
|
|
2049
|
+
context,
|
|
2050
|
+
lazyStep,
|
|
2051
|
+
resultStep,
|
|
2052
|
+
invokeCount,
|
|
2053
|
+
concurrency,
|
|
2054
|
+
telemetry,
|
|
2055
|
+
dispatchDebug,
|
|
2056
|
+
nextStepSettings
|
|
2057
|
+
}) => {
|
|
1954
2058
|
const { headers } = lazyStep.getHeaders({
|
|
1955
2059
|
context,
|
|
1956
2060
|
step: resultStep,
|
|
1957
2061
|
invokeCount,
|
|
1958
|
-
telemetry
|
|
2062
|
+
telemetry,
|
|
2063
|
+
stepSettings: nextStepSettings
|
|
1959
2064
|
});
|
|
1960
2065
|
const body = lazyStep.getBody({
|
|
1961
2066
|
context,
|
|
@@ -1980,6 +2085,138 @@ var submitSingleStep = async ({
|
|
|
1980
2085
|
}
|
|
1981
2086
|
return resultStep;
|
|
1982
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
|
+
};
|
|
1983
2220
|
|
|
1984
2221
|
// src/context/auto-executor.ts
|
|
1985
2222
|
var AutoExecutor = class _AutoExecutor {
|
|
@@ -1996,6 +2233,25 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1996
2233
|
stepCount = 0;
|
|
1997
2234
|
planStepCount = 0;
|
|
1998
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;
|
|
1999
2255
|
/**
|
|
2000
2256
|
* @param context workflow context
|
|
2001
2257
|
* @param steps list of steps
|
|
@@ -2003,14 +2259,17 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2003
2259
|
* @param dispatchLifecycle lifecycle event dispatcher
|
|
2004
2260
|
* @param telemetry optional telemetry information
|
|
2005
2261
|
* @param invokeCount optional invoke count
|
|
2262
|
+
* @param effectiveConfig configuration QStash applied to the delivery
|
|
2263
|
+
* being handled
|
|
2006
2264
|
*/
|
|
2007
|
-
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount) {
|
|
2265
|
+
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount, effectiveConfig) {
|
|
2008
2266
|
this.context = context;
|
|
2009
2267
|
this.steps = steps;
|
|
2010
2268
|
this.dispatchDebug = dispatchDebug;
|
|
2011
2269
|
this.dispatchLifecycle = dispatchLifecycle;
|
|
2012
2270
|
this.telemetry = telemetry;
|
|
2013
2271
|
this.invokeCount = invokeCount ?? 0;
|
|
2272
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2014
2273
|
this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
|
|
2015
2274
|
}
|
|
2016
2275
|
/**
|
|
@@ -2045,6 +2304,15 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2045
2304
|
lazyStepList.push(stepInfo);
|
|
2046
2305
|
const index = this.indexInCurrentList++;
|
|
2047
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
|
+
}
|
|
2048
2316
|
if (!this.promises.has(lazyStepList)) {
|
|
2049
2317
|
const promise2 = this.getExecutionPromise(lazyStepList);
|
|
2050
2318
|
this.promises.set(lazyStepList, promise2);
|
|
@@ -2098,18 +2366,100 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2098
2366
|
}
|
|
2099
2367
|
return parsedOut;
|
|
2100
2368
|
}
|
|
2101
|
-
const
|
|
2102
|
-
|
|
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({
|
|
2103
2388
|
lazyStep,
|
|
2104
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,
|
|
2105
2404
|
invokeCount: this.invokeCount,
|
|
2106
|
-
concurrency:
|
|
2405
|
+
concurrency: NO_CONCURRENCY,
|
|
2107
2406
|
telemetry: this.telemetry,
|
|
2108
|
-
dispatchDebug: this.dispatchDebug
|
|
2109
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2407
|
+
dispatchDebug: this.dispatchDebug
|
|
2110
2408
|
});
|
|
2111
2409
|
throw new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2112
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
|
+
}
|
|
2113
2463
|
/**
|
|
2114
2464
|
* Runs steps in parallel.
|
|
2115
2465
|
*
|
|
@@ -2158,15 +2508,20 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2158
2508
|
validateStep(parallelSteps[stepIndex], planStep);
|
|
2159
2509
|
try {
|
|
2160
2510
|
const parallelStep = parallelSteps[stepIndex];
|
|
2161
|
-
const resultStep = await
|
|
2162
|
-
context: this.context,
|
|
2511
|
+
const resultStep = await executeStep({
|
|
2163
2512
|
lazyStep: parallelStep,
|
|
2164
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,
|
|
2165
2521
|
invokeCount: this.invokeCount,
|
|
2166
2522
|
concurrency: parallelSteps.length,
|
|
2167
2523
|
telemetry: this.telemetry,
|
|
2168
|
-
dispatchDebug: this.dispatchDebug
|
|
2169
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2524
|
+
dispatchDebug: this.dispatchDebug
|
|
2170
2525
|
});
|
|
2171
2526
|
throw new WorkflowAbort(parallelStep.stepName, resultStep);
|
|
2172
2527
|
} catch (error) {
|
|
@@ -2782,6 +3137,46 @@ var WorkflowContext = class {
|
|
|
2782
3137
|
* `1` on the first retry, `2` on the second, and so on.
|
|
2783
3138
|
*/
|
|
2784
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
|
+
}
|
|
2785
3180
|
constructor({
|
|
2786
3181
|
qstashClient,
|
|
2787
3182
|
workflowRunId,
|
|
@@ -2795,7 +3190,8 @@ var WorkflowContext = class {
|
|
|
2795
3190
|
invokeCount,
|
|
2796
3191
|
label,
|
|
2797
3192
|
retried,
|
|
2798
|
-
middlewareManager
|
|
3193
|
+
middlewareManager,
|
|
3194
|
+
effectiveConfig
|
|
2799
3195
|
}) {
|
|
2800
3196
|
this.qstashClient = qstashClient;
|
|
2801
3197
|
this.workflowRunId = workflowRunId;
|
|
@@ -2807,6 +3203,7 @@ var WorkflowContext = class {
|
|
|
2807
3203
|
this.env = env ?? {};
|
|
2808
3204
|
this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
|
|
2809
3205
|
this.retried = retried ?? 0;
|
|
3206
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2810
3207
|
const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
|
|
2811
3208
|
middlewareManagerInstance.assignContext(this);
|
|
2812
3209
|
this.executor = new AutoExecutor(
|
|
@@ -2815,7 +3212,8 @@ var WorkflowContext = class {
|
|
|
2815
3212
|
middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
|
|
2816
3213
|
middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
|
|
2817
3214
|
telemetry,
|
|
2818
|
-
invokeCount
|
|
3215
|
+
invokeCount,
|
|
3216
|
+
this.effectiveConfig
|
|
2819
3217
|
);
|
|
2820
3218
|
}
|
|
2821
3219
|
/**
|
|
@@ -2841,13 +3239,35 @@ var WorkflowContext = class {
|
|
|
2841
3239
|
* ])
|
|
2842
3240
|
* ```
|
|
2843
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
|
+
*
|
|
2844
3258
|
* @param stepName name of the step
|
|
2845
3259
|
* @param stepFunction step function to be executed
|
|
3260
|
+
* @param stepSettings step-level settings for this step
|
|
2846
3261
|
* @returns result of the step function
|
|
2847
3262
|
*/
|
|
2848
|
-
async run(stepName, stepFunction) {
|
|
3263
|
+
async run(stepName, stepFunction, stepSettings) {
|
|
3264
|
+
if (stepSettings) {
|
|
3265
|
+
validateFlowControl(stepSettings.flowControl);
|
|
3266
|
+
}
|
|
2849
3267
|
const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
|
|
2850
|
-
return await this.addStep(
|
|
3268
|
+
return await this.addStep(
|
|
3269
|
+
new LazyFunctionStep(this, stepName, wrappedStepFunction, stepSettings)
|
|
3270
|
+
);
|
|
2851
3271
|
}
|
|
2852
3272
|
/**
|
|
2853
3273
|
* Stops the execution for the duration provided.
|
|
@@ -3070,7 +3490,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3070
3490
|
*
|
|
3071
3491
|
* @param routeFunction
|
|
3072
3492
|
*/
|
|
3073
|
-
static async tryAuthentication(routeFunction, context) {
|
|
3493
|
+
static async tryAuthentication(routeFunction, context, effectiveConfig) {
|
|
3074
3494
|
const disabledContext = new _DisabledWorkflowContext({
|
|
3075
3495
|
qstashClient: new import_qstash10.Client({
|
|
3076
3496
|
baseUrl: "disabled-client",
|
|
@@ -3085,7 +3505,10 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3085
3505
|
initialPayload: context.requestPayload,
|
|
3086
3506
|
env: context.env,
|
|
3087
3507
|
label: context.labels,
|
|
3088
|
-
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
|
|
3089
3512
|
});
|
|
3090
3513
|
try {
|
|
3091
3514
|
await routeFunction(disabledContext);
|
|
@@ -3302,6 +3725,7 @@ var handleFailure = async ({
|
|
|
3302
3725
|
}
|
|
3303
3726
|
const userHeaders = recreateUserHeaders(request.headers);
|
|
3304
3727
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3728
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3305
3729
|
const workflowContext = new WorkflowContext({
|
|
3306
3730
|
qstashClient,
|
|
3307
3731
|
workflowRunId,
|
|
@@ -3315,11 +3739,13 @@ var handleFailure = async ({
|
|
|
3315
3739
|
label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
|
|
3316
3740
|
retried,
|
|
3317
3741
|
workflowRunCreatedAt: workflowCreatedAt,
|
|
3318
|
-
middlewareManager: void 0
|
|
3742
|
+
middlewareManager: void 0,
|
|
3743
|
+
effectiveConfig
|
|
3319
3744
|
});
|
|
3320
3745
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3321
3746
|
routeFunction,
|
|
3322
|
-
workflowContext
|
|
3747
|
+
workflowContext,
|
|
3748
|
+
effectiveConfig
|
|
3323
3749
|
);
|
|
3324
3750
|
if (authCheck.isErr()) {
|
|
3325
3751
|
await dispatchDebug?.("onError", {
|
|
@@ -3411,7 +3837,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
|
|
|
3411
3837
|
const client = new import_qstash11.Client({
|
|
3412
3838
|
...clientOptions,
|
|
3413
3839
|
baseUrl: clientEnv.QSTASH_URL,
|
|
3414
|
-
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)
|
|
3415
3847
|
});
|
|
3416
3848
|
const receiver = getReceiver(environment, receiverConfig, region);
|
|
3417
3849
|
return { client, receiver };
|
|
@@ -3461,7 +3893,9 @@ var getQStashHandlers = ({
|
|
|
3461
3893
|
client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
|
|
3462
3894
|
...qstashClientOption,
|
|
3463
3895
|
baseUrl: environment.QSTASH_URL,
|
|
3464
|
-
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)
|
|
3465
3899
|
}),
|
|
3466
3900
|
receiver: getReceiver(environment, receiverConfig)
|
|
3467
3901
|
}
|
|
@@ -3801,12 +4235,14 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3801
4235
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3802
4236
|
const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
|
|
3803
4237
|
const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
4238
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3804
4239
|
const workflowContext = new WorkflowContext({
|
|
3805
4240
|
qstashClient: regionalClient,
|
|
3806
4241
|
workflowRunId,
|
|
3807
4242
|
initialPayload: isThirdPartyCallResult(request) ? JSON.parse(rawInitialPayload) : initialPayloadParser(rawInitialPayload),
|
|
3808
4243
|
headers: recreateUserHeaders(request.headers),
|
|
3809
4244
|
steps,
|
|
4245
|
+
effectiveConfig,
|
|
3810
4246
|
url: workflowUrl,
|
|
3811
4247
|
env,
|
|
3812
4248
|
telemetry,
|
|
@@ -3818,7 +4254,8 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3818
4254
|
});
|
|
3819
4255
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3820
4256
|
routeFunction,
|
|
3821
|
-
workflowContext
|
|
4257
|
+
workflowContext,
|
|
4258
|
+
effectiveConfig
|
|
3822
4259
|
);
|
|
3823
4260
|
if (authCheck.isErr()) {
|
|
3824
4261
|
throw authCheck.error;
|
|
@@ -3855,7 +4292,23 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3855
4292
|
if (steps.length === 1) {
|
|
3856
4293
|
await middlewareManager.dispatchLifecycle("runStarted", {});
|
|
3857
4294
|
}
|
|
3858
|
-
|
|
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;
|
|
3859
4312
|
},
|
|
3860
4313
|
onCleanup: async (result2) => {
|
|
3861
4314
|
await middlewareManager.dispatchLifecycle("runCompleted", {
|
|
@@ -3936,6 +4389,7 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3936
4389
|
};
|
|
3937
4390
|
|
|
3938
4391
|
// platforms/nextjs.ts
|
|
4392
|
+
var import_qstash13 = require("@upstash/qstash");
|
|
3939
4393
|
var appTelemetry = {
|
|
3940
4394
|
sdk: SDK_TELEMETRY,
|
|
3941
4395
|
framework: "nextjs",
|
|
@@ -3947,6 +4401,7 @@ var pagesTelemetry = {
|
|
|
3947
4401
|
runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
|
|
3948
4402
|
};
|
|
3949
4403
|
var serve = (routeFunction, options) => {
|
|
4404
|
+
void (0, import_qstash13.startDevServer)();
|
|
3950
4405
|
const { handler: serveHandler } = serveBase(
|
|
3951
4406
|
routeFunction,
|
|
3952
4407
|
appTelemetry,
|
|
@@ -3979,6 +4434,7 @@ var serveMany = (workflows, options) => {
|
|
|
3979
4434
|
};
|
|
3980
4435
|
};
|
|
3981
4436
|
var servePagesRouter = (routeFunction, options) => {
|
|
4437
|
+
void (0, import_qstash13.startDevServer)();
|
|
3982
4438
|
const { handler: serveHandler } = serveBase(routeFunction, pagesTelemetry, options, void 0);
|
|
3983
4439
|
const handler = async (request_, res) => {
|
|
3984
4440
|
if (request_.method?.toUpperCase() !== "POST") {
|