@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/astro.js
CHANGED
|
@@ -26,6 +26,9 @@ __export(astro_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(astro_exports);
|
|
28
28
|
|
|
29
|
+
// src/serve/index.ts
|
|
30
|
+
var import_qstash12 = require("@upstash/qstash");
|
|
31
|
+
|
|
29
32
|
// src/client/utils.ts
|
|
30
33
|
var import_qstash2 = require("@upstash/qstash");
|
|
31
34
|
|
|
@@ -142,8 +145,15 @@ function isInstanceOf(v, ctor) {
|
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
// src/client/utils.ts
|
|
148
|
+
var assertNonEmptyId = (id, label = "id") => {
|
|
149
|
+
if (!id) {
|
|
150
|
+
throw new import_qstash2.QstashError(`${label} cannot be empty`);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
145
153
|
var makeNotifyRequest = async (requester, eventId, eventData, workflowRunId) => {
|
|
146
|
-
|
|
154
|
+
assertNonEmptyId(eventId, "Event id");
|
|
155
|
+
if (workflowRunId !== void 0) assertNonEmptyId(workflowRunId, "Workflow run id");
|
|
156
|
+
const path = workflowRunId === void 0 ? ["v2", "notify", eventId] : ["v2", "notify", workflowRunId, eventId];
|
|
147
157
|
const result = await requester.request({
|
|
148
158
|
path,
|
|
149
159
|
method: "POST",
|
|
@@ -207,6 +217,14 @@ var WORKFLOW_CREATED_AT_HEADER = "Upstash-Workflow-CreatedAt";
|
|
|
207
217
|
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
|
|
208
218
|
var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
|
|
209
219
|
var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
|
|
220
|
+
var WORKFLOW_FEATURE_SET = "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig";
|
|
221
|
+
var WORKFLOW_STEP_CONFIG_CALL_TYPE = "stepConfig";
|
|
222
|
+
var WORKFLOW_STEP_CONFIG_HEADER = "Upstash-Workflow-Step-Config";
|
|
223
|
+
var FLOW_CONTROL_KEY_HEADER = "Upstash-Flow-Control-Key";
|
|
224
|
+
var FLOW_CONTROL_VALUE_HEADER = "Upstash-Flow-Control-Value";
|
|
225
|
+
var RETRIES_HEADER = "Upstash-Retries";
|
|
226
|
+
var RETRY_DELAY_HEADER = "Upstash-Retry-Delay";
|
|
227
|
+
var WORKFLOW_STEP_CONFIG_FEATURE = "WF_StepConfig";
|
|
210
228
|
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
|
|
211
229
|
var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
|
|
212
230
|
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
|
|
@@ -224,102 +242,6 @@ var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
|
|
|
224
242
|
var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
|
|
225
243
|
var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
|
|
226
244
|
|
|
227
|
-
// src/context/auto-executor.ts
|
|
228
|
-
var import_qstash5 = require("@upstash/qstash");
|
|
229
|
-
|
|
230
|
-
// src/qstash/headers.ts
|
|
231
|
-
var import_qstash4 = require("@upstash/qstash");
|
|
232
|
-
|
|
233
|
-
// src/utils.ts
|
|
234
|
-
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
235
|
-
var NANOID_LENGTH = 21;
|
|
236
|
-
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
237
|
-
function validateLabel(label) {
|
|
238
|
-
if (label === void 0) return;
|
|
239
|
-
const labels = Array.isArray(label) ? label : [label];
|
|
240
|
-
if (labels.length === 0) {
|
|
241
|
-
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
242
|
-
}
|
|
243
|
-
for (const value of labels) {
|
|
244
|
-
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
245
|
-
throw new WorkflowNonRetryableError(
|
|
246
|
-
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function serializeLabel(label) {
|
|
252
|
-
return Array.isArray(label) ? label.join(",") : label;
|
|
253
|
-
}
|
|
254
|
-
function validateFlowControl(flowControl) {
|
|
255
|
-
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
256
|
-
throw new WorkflowNonRetryableError(
|
|
257
|
-
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function getRandomInt() {
|
|
262
|
-
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
263
|
-
}
|
|
264
|
-
function nanoid(length = NANOID_LENGTH) {
|
|
265
|
-
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
266
|
-
}
|
|
267
|
-
function getWorkflowRunId(id) {
|
|
268
|
-
return `wfr_${id ?? nanoid()}`;
|
|
269
|
-
}
|
|
270
|
-
function decodeBase64(base64) {
|
|
271
|
-
const binString = atob(base64);
|
|
272
|
-
try {
|
|
273
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
274
|
-
return new TextDecoder().decode(intArray);
|
|
275
|
-
} catch (error) {
|
|
276
|
-
console.warn(
|
|
277
|
-
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
278
|
-
);
|
|
279
|
-
return binString;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
function getUserIdFromToken(qstashClient) {
|
|
283
|
-
try {
|
|
284
|
-
const token = qstashClient.token;
|
|
285
|
-
const decodedToken = decodeBase64(token);
|
|
286
|
-
const tokenPayload = JSON.parse(decodedToken);
|
|
287
|
-
const userId = tokenPayload.UserID;
|
|
288
|
-
if (!userId) {
|
|
289
|
-
throw new WorkflowError("QStash token payload does not contain userId");
|
|
290
|
-
}
|
|
291
|
-
return userId;
|
|
292
|
-
} catch (error) {
|
|
293
|
-
throw new WorkflowError(
|
|
294
|
-
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function getQStashUrl(qstashClient) {
|
|
299
|
-
try {
|
|
300
|
-
const requester = qstashClient.http;
|
|
301
|
-
const baseUrl = requester.baseUrl;
|
|
302
|
-
if (!baseUrl) {
|
|
303
|
-
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
304
|
-
}
|
|
305
|
-
return baseUrl;
|
|
306
|
-
} catch (error) {
|
|
307
|
-
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
function getEventId() {
|
|
311
|
-
return `evt_${nanoid(15)}`;
|
|
312
|
-
}
|
|
313
|
-
function stringifyBody(body) {
|
|
314
|
-
if (body === void 0) {
|
|
315
|
-
return void 0;
|
|
316
|
-
}
|
|
317
|
-
if (typeof body === "string") {
|
|
318
|
-
return body;
|
|
319
|
-
}
|
|
320
|
-
return JSON.stringify(body);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
245
|
// node_modules/neverthrow/dist/index.es.js
|
|
324
246
|
var defaultErrorConfig = {
|
|
325
247
|
withStackTrace: false
|
|
@@ -735,6 +657,102 @@ var Err = class {
|
|
|
735
657
|
};
|
|
736
658
|
var fromThrowable = Result.fromThrowable;
|
|
737
659
|
|
|
660
|
+
// src/context/auto-executor.ts
|
|
661
|
+
var import_qstash5 = require("@upstash/qstash");
|
|
662
|
+
|
|
663
|
+
// src/qstash/headers.ts
|
|
664
|
+
var import_qstash4 = require("@upstash/qstash");
|
|
665
|
+
|
|
666
|
+
// src/utils.ts
|
|
667
|
+
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
668
|
+
var NANOID_LENGTH = 21;
|
|
669
|
+
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
670
|
+
function validateLabel(label) {
|
|
671
|
+
if (label === void 0) return;
|
|
672
|
+
const labels = Array.isArray(label) ? label : [label];
|
|
673
|
+
if (labels.length === 0) {
|
|
674
|
+
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
675
|
+
}
|
|
676
|
+
for (const value of labels) {
|
|
677
|
+
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
678
|
+
throw new WorkflowNonRetryableError(
|
|
679
|
+
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function serializeLabel(label) {
|
|
685
|
+
return Array.isArray(label) ? label.join(",") : label;
|
|
686
|
+
}
|
|
687
|
+
function validateFlowControl(flowControl) {
|
|
688
|
+
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
689
|
+
throw new WorkflowNonRetryableError(
|
|
690
|
+
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function getRandomInt() {
|
|
695
|
+
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
696
|
+
}
|
|
697
|
+
function nanoid(length = NANOID_LENGTH) {
|
|
698
|
+
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
699
|
+
}
|
|
700
|
+
function getWorkflowRunId(id) {
|
|
701
|
+
return `wfr_${id ?? nanoid()}`;
|
|
702
|
+
}
|
|
703
|
+
function decodeBase64(base64) {
|
|
704
|
+
const binString = atob(base64);
|
|
705
|
+
try {
|
|
706
|
+
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
707
|
+
return new TextDecoder().decode(intArray);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
console.warn(
|
|
710
|
+
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
711
|
+
);
|
|
712
|
+
return binString;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function getUserIdFromToken(qstashClient) {
|
|
716
|
+
try {
|
|
717
|
+
const token = qstashClient.token;
|
|
718
|
+
const decodedToken = decodeBase64(token);
|
|
719
|
+
const tokenPayload = JSON.parse(decodedToken);
|
|
720
|
+
const userId = tokenPayload.UserID;
|
|
721
|
+
if (!userId) {
|
|
722
|
+
throw new WorkflowError("QStash token payload does not contain userId");
|
|
723
|
+
}
|
|
724
|
+
return userId;
|
|
725
|
+
} catch (error) {
|
|
726
|
+
throw new WorkflowError(
|
|
727
|
+
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function getQStashUrl(qstashClient) {
|
|
732
|
+
try {
|
|
733
|
+
const requester = qstashClient.http;
|
|
734
|
+
const baseUrl = requester.baseUrl;
|
|
735
|
+
if (!baseUrl) {
|
|
736
|
+
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
737
|
+
}
|
|
738
|
+
return baseUrl;
|
|
739
|
+
} catch (error) {
|
|
740
|
+
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function getEventId() {
|
|
744
|
+
return `evt_${nanoid(15)}`;
|
|
745
|
+
}
|
|
746
|
+
function stringifyBody(body) {
|
|
747
|
+
if (body === void 0) {
|
|
748
|
+
return void 0;
|
|
749
|
+
}
|
|
750
|
+
if (typeof body === "string") {
|
|
751
|
+
return body;
|
|
752
|
+
}
|
|
753
|
+
return JSON.stringify(body);
|
|
754
|
+
}
|
|
755
|
+
|
|
738
756
|
// src/types.ts
|
|
739
757
|
var StepTypes = [
|
|
740
758
|
"Initial",
|
|
@@ -840,6 +858,10 @@ var triggerFirstInvocation = async (params) => {
|
|
|
840
858
|
return err(error_);
|
|
841
859
|
}
|
|
842
860
|
};
|
|
861
|
+
var flushPendingStep = async (workflowContext) => {
|
|
862
|
+
const { executor } = workflowContext;
|
|
863
|
+
return await executor.submitPendingStep();
|
|
864
|
+
};
|
|
843
865
|
var triggerRouteFunction = async ({
|
|
844
866
|
onCleanup,
|
|
845
867
|
onStep,
|
|
@@ -1047,8 +1069,29 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
|
|
|
1047
1069
|
var BaseLazyStep = class _BaseLazyStep {
|
|
1048
1070
|
stepName;
|
|
1049
1071
|
context;
|
|
1050
|
-
|
|
1072
|
+
/**
|
|
1073
|
+
* step-level settings (flow control, retries etc.) which override the
|
|
1074
|
+
* settings the workflow run was triggered with, for this step only.
|
|
1075
|
+
*/
|
|
1076
|
+
stepSettings;
|
|
1077
|
+
/**
|
|
1078
|
+
* whether this step's result can be submitted after the route function
|
|
1079
|
+
* has moved on, instead of right away.
|
|
1080
|
+
*
|
|
1081
|
+
* Enabled for steps whose `getResultStep` produces the final `out` —
|
|
1082
|
+
* which is not the same as "needs no server round trip":
|
|
1083
|
+
* `LazyCreateWebhookStep` builds its result in `getBody` and returns
|
|
1084
|
+
* `out: undefined` from `getResultStep`, so deferring it would hand
|
|
1085
|
+
* `undefined` to the route function.
|
|
1086
|
+
*
|
|
1087
|
+
* Deferring lets the route function continue and reveal the next step,
|
|
1088
|
+
* so that step's settings can ride on this step's submission instead
|
|
1089
|
+
* of needing a step config request of their own.
|
|
1090
|
+
*/
|
|
1091
|
+
supportsDeferredSubmission = false;
|
|
1092
|
+
constructor(context, stepName, stepSettings) {
|
|
1051
1093
|
this.context = context;
|
|
1094
|
+
this.stepSettings = stepSettings;
|
|
1052
1095
|
if (!stepName) {
|
|
1053
1096
|
throw new WorkflowError(
|
|
1054
1097
|
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
|
|
@@ -1112,7 +1155,13 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1112
1155
|
step.out = JSON.stringify(step.out);
|
|
1113
1156
|
return JSON.stringify(step);
|
|
1114
1157
|
}
|
|
1115
|
-
getHeaders({
|
|
1158
|
+
getHeaders({
|
|
1159
|
+
context,
|
|
1160
|
+
telemetry: telemetry2,
|
|
1161
|
+
invokeCount,
|
|
1162
|
+
step,
|
|
1163
|
+
stepSettings
|
|
1164
|
+
}) {
|
|
1116
1165
|
return getHeaders({
|
|
1117
1166
|
initHeaderValue: "false",
|
|
1118
1167
|
workflowConfig: {
|
|
@@ -1125,7 +1174,8 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1125
1174
|
stepInfo: {
|
|
1126
1175
|
step,
|
|
1127
1176
|
lazyStep: this
|
|
1128
|
-
}
|
|
1177
|
+
},
|
|
1178
|
+
stepSettings
|
|
1129
1179
|
});
|
|
1130
1180
|
}
|
|
1131
1181
|
async submitStep({ context, body, headers }) {
|
|
@@ -1143,8 +1193,9 @@ var LazyFunctionStep = class extends BaseLazyStep {
|
|
|
1143
1193
|
stepFunction;
|
|
1144
1194
|
stepType = "Run";
|
|
1145
1195
|
allowUndefinedOut = true;
|
|
1146
|
-
|
|
1147
|
-
|
|
1196
|
+
supportsDeferredSubmission = true;
|
|
1197
|
+
constructor(context, stepName, stepFunction, stepSettings) {
|
|
1198
|
+
super(context, stepName, stepSettings);
|
|
1148
1199
|
this.stepFunction = stepFunction;
|
|
1149
1200
|
}
|
|
1150
1201
|
getPlanStep(concurrent, targetStep) {
|
|
@@ -1174,6 +1225,7 @@ var LazySleepStep = class extends BaseLazyStep {
|
|
|
1174
1225
|
sleep;
|
|
1175
1226
|
stepType = "SleepFor";
|
|
1176
1227
|
allowUndefinedOut = true;
|
|
1228
|
+
supportsDeferredSubmission = true;
|
|
1177
1229
|
constructor(context, stepName, sleep) {
|
|
1178
1230
|
super(context, stepName);
|
|
1179
1231
|
this.sleep = sleep;
|
|
@@ -1213,6 +1265,7 @@ var LazySleepUntilStep = class extends BaseLazyStep {
|
|
|
1213
1265
|
sleepUntil;
|
|
1214
1266
|
stepType = "SleepUntil";
|
|
1215
1267
|
allowUndefinedOut = true;
|
|
1268
|
+
supportsDeferredSubmission = true;
|
|
1216
1269
|
constructor(context, stepName, sleepUntil) {
|
|
1217
1270
|
super(context, stepName);
|
|
1218
1271
|
this.sleepUntil = sleepUntil;
|
|
@@ -1705,6 +1758,7 @@ var WorkflowHeaders = class {
|
|
|
1705
1758
|
invokeCount;
|
|
1706
1759
|
initHeaderValue;
|
|
1707
1760
|
stepInfo;
|
|
1761
|
+
stepSettings;
|
|
1708
1762
|
headers;
|
|
1709
1763
|
/**
|
|
1710
1764
|
* @param params workflow header parameters
|
|
@@ -1714,17 +1768,20 @@ var WorkflowHeaders = class {
|
|
|
1714
1768
|
workflowConfig,
|
|
1715
1769
|
invokeCount,
|
|
1716
1770
|
initHeaderValue,
|
|
1717
|
-
stepInfo
|
|
1771
|
+
stepInfo,
|
|
1772
|
+
stepSettings
|
|
1718
1773
|
}) {
|
|
1719
1774
|
this.userHeaders = userHeaders;
|
|
1720
1775
|
this.workflowConfig = workflowConfig;
|
|
1721
1776
|
this.invokeCount = invokeCount;
|
|
1722
1777
|
this.initHeaderValue = initHeaderValue;
|
|
1723
1778
|
this.stepInfo = stepInfo;
|
|
1779
|
+
this.stepSettings = stepSettings;
|
|
1724
1780
|
this.headers = {
|
|
1725
1781
|
rawHeaders: {},
|
|
1726
1782
|
workflowHeaders: {},
|
|
1727
|
-
failureHeaders: {}
|
|
1783
|
+
failureHeaders: {},
|
|
1784
|
+
stepSettingsHeaders: {}
|
|
1728
1785
|
};
|
|
1729
1786
|
}
|
|
1730
1787
|
getHeaders() {
|
|
@@ -1735,6 +1792,7 @@ var WorkflowHeaders = class {
|
|
|
1735
1792
|
this.addUserHeaders();
|
|
1736
1793
|
this.addInvokeCount();
|
|
1737
1794
|
this.addFailureUrl();
|
|
1795
|
+
this.addStepSettings();
|
|
1738
1796
|
const contentType = this.addContentType();
|
|
1739
1797
|
return this.prefixHeaders(contentType);
|
|
1740
1798
|
}
|
|
@@ -1744,7 +1802,7 @@ var WorkflowHeaders = class {
|
|
|
1744
1802
|
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
|
|
1745
1803
|
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
|
|
1746
1804
|
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
|
|
1747
|
-
[WORKFLOW_FEATURE_HEADER]:
|
|
1805
|
+
[WORKFLOW_FEATURE_HEADER]: WORKFLOW_FEATURE_SET,
|
|
1748
1806
|
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
1749
1807
|
...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
|
|
1750
1808
|
};
|
|
@@ -1822,7 +1880,7 @@ var WorkflowHeaders = class {
|
|
|
1822
1880
|
this.headers.failureHeaders["Workflow-Init"] = "false";
|
|
1823
1881
|
this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
|
|
1824
1882
|
this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
|
|
1825
|
-
this.headers.failureHeaders["Feature-Set"] =
|
|
1883
|
+
this.headers.failureHeaders["Feature-Set"] = WORKFLOW_FEATURE_SET;
|
|
1826
1884
|
if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
|
|
1827
1885
|
this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
|
|
1828
1886
|
}
|
|
@@ -1830,6 +1888,42 @@ var WorkflowHeaders = class {
|
|
|
1830
1888
|
this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
|
|
1831
1889
|
}
|
|
1832
1890
|
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Applies the step-level settings of a step to the message.
|
|
1893
|
+
*
|
|
1894
|
+
* These headers configure the message itself, so that QStash uses them
|
|
1895
|
+
* instead of the settings the run was triggered with. The delivery of
|
|
1896
|
+
* the message is what executes the step, which is how a step's
|
|
1897
|
+
* settings reach it.
|
|
1898
|
+
*
|
|
1899
|
+
* When any setting is present the feature set is extended with
|
|
1900
|
+
* `WF_StepConfig`, which is what tells QStash to keep them. QStash
|
|
1901
|
+
* reports that back on the delivery as `Upstash-Workflow-Step-Config`.
|
|
1902
|
+
*/
|
|
1903
|
+
addStepSettings() {
|
|
1904
|
+
if (!this.stepSettings) {
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
const headers = {};
|
|
1908
|
+
if (this.stepSettings.flowControl) {
|
|
1909
|
+
const { flowControlKey, flowControlValue } = prepareFlowControl(
|
|
1910
|
+
this.stepSettings.flowControl
|
|
1911
|
+
);
|
|
1912
|
+
headers[FLOW_CONTROL_KEY_HEADER] = flowControlKey;
|
|
1913
|
+
headers[FLOW_CONTROL_VALUE_HEADER] = flowControlValue;
|
|
1914
|
+
}
|
|
1915
|
+
if (this.stepSettings.retries !== void 0) {
|
|
1916
|
+
headers[RETRIES_HEADER] = this.stepSettings.retries.toString();
|
|
1917
|
+
}
|
|
1918
|
+
if (this.stepSettings.retryDelay) {
|
|
1919
|
+
headers[RETRY_DELAY_HEADER] = this.stepSettings.retryDelay;
|
|
1920
|
+
}
|
|
1921
|
+
if (Object.keys(headers).length === 0) {
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
headers[WORKFLOW_FEATURE_HEADER] = `${WORKFLOW_FEATURE_SET},${WORKFLOW_STEP_CONFIG_FEATURE}`;
|
|
1925
|
+
this.headers.stepSettingsHeaders = headers;
|
|
1926
|
+
}
|
|
1833
1927
|
addContentType() {
|
|
1834
1928
|
if (this.workflowConfig.useJSONContent) {
|
|
1835
1929
|
this.headers.rawHeaders["content-type"] = "application/json";
|
|
@@ -1843,14 +1937,17 @@ var WorkflowHeaders = class {
|
|
|
1843
1937
|
return contentType;
|
|
1844
1938
|
}
|
|
1845
1939
|
prefixHeaders(contentType) {
|
|
1846
|
-
const { rawHeaders, workflowHeaders, failureHeaders } = this.headers;
|
|
1940
|
+
const { rawHeaders, workflowHeaders, failureHeaders, stepSettingsHeaders } = this.headers;
|
|
1847
1941
|
const isCall = this.stepInfo?.lazyStep.stepType === "Call";
|
|
1848
1942
|
return {
|
|
1849
1943
|
headers: {
|
|
1850
1944
|
...rawHeaders,
|
|
1851
1945
|
...addPrefixToHeaders(workflowHeaders, isCall ? "Upstash-Callback-" : "Upstash-"),
|
|
1852
1946
|
...addPrefixToHeaders(failureHeaders, "Upstash-Failure-Callback-"),
|
|
1853
|
-
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {}
|
|
1947
|
+
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {},
|
|
1948
|
+
// last, so they override the run's settings above. Never
|
|
1949
|
+
// prefixed: they configure this message, not its callback.
|
|
1950
|
+
...stepSettingsHeaders
|
|
1854
1951
|
},
|
|
1855
1952
|
contentType
|
|
1856
1953
|
};
|
|
@@ -1901,7 +1998,7 @@ var submitParallelSteps = async ({
|
|
|
1901
1998
|
info: `Submitting ${planSteps.length} parallel steps.`
|
|
1902
1999
|
});
|
|
1903
2000
|
const result = await context.qstashClient.batch(
|
|
1904
|
-
planSteps.map((planStep) => {
|
|
2001
|
+
planSteps.map((planStep, index) => {
|
|
1905
2002
|
const { headers } = getHeaders({
|
|
1906
2003
|
initHeaderValue: "false",
|
|
1907
2004
|
workflowConfig: {
|
|
@@ -1909,7 +2006,8 @@ var submitParallelSteps = async ({
|
|
|
1909
2006
|
workflowUrl: context.url,
|
|
1910
2007
|
telemetry: telemetry2
|
|
1911
2008
|
},
|
|
1912
|
-
invokeCount
|
|
2009
|
+
invokeCount,
|
|
2010
|
+
stepSettings: steps[index].stepSettings
|
|
1913
2011
|
});
|
|
1914
2012
|
return {
|
|
1915
2013
|
headers,
|
|
@@ -1928,31 +2026,38 @@ var submitParallelSteps = async ({
|
|
|
1928
2026
|
}
|
|
1929
2027
|
throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
|
|
1930
2028
|
};
|
|
1931
|
-
var
|
|
1932
|
-
context,
|
|
2029
|
+
var executeStep = async ({
|
|
1933
2030
|
lazyStep,
|
|
1934
2031
|
stepId,
|
|
1935
|
-
invokeCount,
|
|
1936
2032
|
concurrency,
|
|
1937
|
-
telemetry: telemetry2,
|
|
1938
|
-
dispatchDebug,
|
|
1939
2033
|
dispatchLifecycle
|
|
1940
2034
|
}) => {
|
|
1941
2035
|
await dispatchLifecycle("beforeExecution", {
|
|
1942
2036
|
stepName: lazyStep.stepName
|
|
1943
2037
|
});
|
|
1944
|
-
let resultStep;
|
|
1945
2038
|
try {
|
|
1946
|
-
|
|
2039
|
+
return await lazyStep.getResultStep(concurrency, stepId);
|
|
1947
2040
|
} catch (error) {
|
|
1948
2041
|
attachStepNameToError(error, lazyStep.stepName);
|
|
1949
2042
|
throw error;
|
|
1950
2043
|
}
|
|
2044
|
+
};
|
|
2045
|
+
var submitStepResult = async ({
|
|
2046
|
+
context,
|
|
2047
|
+
lazyStep,
|
|
2048
|
+
resultStep,
|
|
2049
|
+
invokeCount,
|
|
2050
|
+
concurrency,
|
|
2051
|
+
telemetry: telemetry2,
|
|
2052
|
+
dispatchDebug,
|
|
2053
|
+
nextStepSettings
|
|
2054
|
+
}) => {
|
|
1951
2055
|
const { headers } = lazyStep.getHeaders({
|
|
1952
2056
|
context,
|
|
1953
2057
|
step: resultStep,
|
|
1954
2058
|
invokeCount,
|
|
1955
|
-
telemetry: telemetry2
|
|
2059
|
+
telemetry: telemetry2,
|
|
2060
|
+
stepSettings: nextStepSettings
|
|
1956
2061
|
});
|
|
1957
2062
|
const body = lazyStep.getBody({
|
|
1958
2063
|
context,
|
|
@@ -1977,6 +2082,138 @@ var submitSingleStep = async ({
|
|
|
1977
2082
|
}
|
|
1978
2083
|
return resultStep;
|
|
1979
2084
|
};
|
|
2085
|
+
var publishStepConfigRequest = async ({
|
|
2086
|
+
context,
|
|
2087
|
+
lazyStep,
|
|
2088
|
+
targetStep,
|
|
2089
|
+
invokeCount,
|
|
2090
|
+
telemetry: telemetry2,
|
|
2091
|
+
dispatchDebug
|
|
2092
|
+
}) => {
|
|
2093
|
+
const { headers } = getHeaders({
|
|
2094
|
+
initHeaderValue: "false",
|
|
2095
|
+
workflowConfig: {
|
|
2096
|
+
workflowRunId: context.workflowRunId,
|
|
2097
|
+
workflowUrl: context.url,
|
|
2098
|
+
telemetry: telemetry2
|
|
2099
|
+
},
|
|
2100
|
+
invokeCount,
|
|
2101
|
+
stepSettings: lazyStep.stepSettings
|
|
2102
|
+
});
|
|
2103
|
+
await dispatchDebug("onInfo", {
|
|
2104
|
+
info: `Step "${lazyStep.stepName}" (${targetStep}) has step-level settings which the current delivery does not carry. Requesting a delivery which does.`
|
|
2105
|
+
});
|
|
2106
|
+
const result = await context.qstashClient.publishJSON({
|
|
2107
|
+
headers: {
|
|
2108
|
+
...headers,
|
|
2109
|
+
"Upstash-Workflow-CallType": WORKFLOW_STEP_CONFIG_CALL_TYPE
|
|
2110
|
+
},
|
|
2111
|
+
method: "POST",
|
|
2112
|
+
body: { targetStep, invokeCount },
|
|
2113
|
+
url: context.url,
|
|
2114
|
+
contentBasedDeduplication: true
|
|
2115
|
+
});
|
|
2116
|
+
if (result?.deduplicated) {
|
|
2117
|
+
await dispatchDebug("onWarning", {
|
|
2118
|
+
warning: `A step config request for step "${lazyStep.stepName}" (${targetStep}) was already published and this one was deduplicated. Expected when the delivery which published it is being retried; otherwise the run stops here.`
|
|
2119
|
+
});
|
|
2120
|
+
} else if (result?.messageId) {
|
|
2121
|
+
await dispatchDebug("onInfo", {
|
|
2122
|
+
info: `Requested a delivery under the settings of step "${lazyStep.stepName}" with messageId: ${result.messageId}.`
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
|
|
2127
|
+
// src/qstash/step-config.ts
|
|
2128
|
+
var DEFAULT_PARALLELISM = 0;
|
|
2129
|
+
var DEFAULT_RATE = 0;
|
|
2130
|
+
var DEFAULT_PERIOD_SECONDS = 1;
|
|
2131
|
+
var SECONDS_PER_UNIT = {
|
|
2132
|
+
s: 1,
|
|
2133
|
+
m: 60,
|
|
2134
|
+
h: 60 * 60,
|
|
2135
|
+
d: 24 * 60 * 60
|
|
2136
|
+
};
|
|
2137
|
+
var durationToSeconds = (duration) => {
|
|
2138
|
+
if (typeof duration === "number") {
|
|
2139
|
+
return Number.isFinite(duration) ? duration : void 0;
|
|
2140
|
+
}
|
|
2141
|
+
const match = /^(\d+)([smhd])$/.exec(duration.trim());
|
|
2142
|
+
if (match) {
|
|
2143
|
+
return Number(match[1]) * SECONDS_PER_UNIT[match[2]];
|
|
2144
|
+
}
|
|
2145
|
+
const seconds = Number(duration.trim());
|
|
2146
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2147
|
+
};
|
|
2148
|
+
var normalizeFlowControl = (flowControl) => {
|
|
2149
|
+
const period = flowControl.period === void 0 ? void 0 : durationToSeconds(flowControl.period);
|
|
2150
|
+
return {
|
|
2151
|
+
key: flowControl.key,
|
|
2152
|
+
parallelism: flowControl.parallelism ?? DEFAULT_PARALLELISM,
|
|
2153
|
+
rate: flowControl.rate ?? flowControl.ratePerSecond ?? DEFAULT_RATE,
|
|
2154
|
+
period: period ?? DEFAULT_PERIOD_SECONDS
|
|
2155
|
+
};
|
|
2156
|
+
};
|
|
2157
|
+
var parseFlowControlHeaders = (key, value) => {
|
|
2158
|
+
if (!key) {
|
|
2159
|
+
return void 0;
|
|
2160
|
+
}
|
|
2161
|
+
const flowControl = {
|
|
2162
|
+
key,
|
|
2163
|
+
parallelism: DEFAULT_PARALLELISM,
|
|
2164
|
+
rate: DEFAULT_RATE,
|
|
2165
|
+
period: DEFAULT_PERIOD_SECONDS
|
|
2166
|
+
};
|
|
2167
|
+
for (const entry of (value ?? "").split(",")) {
|
|
2168
|
+
const [name, rawValue] = entry.split("=").map((part) => part.trim());
|
|
2169
|
+
if (!name || rawValue === void 0) {
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
switch (name) {
|
|
2173
|
+
case "parallelism": {
|
|
2174
|
+
flowControl.parallelism = Number(rawValue);
|
|
2175
|
+
break;
|
|
2176
|
+
}
|
|
2177
|
+
case "rate": {
|
|
2178
|
+
flowControl.rate = Number(rawValue);
|
|
2179
|
+
break;
|
|
2180
|
+
}
|
|
2181
|
+
case "period": {
|
|
2182
|
+
flowControl.period = durationToSeconds(rawValue) ?? DEFAULT_PERIOD_SECONDS;
|
|
2183
|
+
break;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return flowControl;
|
|
2188
|
+
};
|
|
2189
|
+
var getEffectiveConfig = (headers) => {
|
|
2190
|
+
const retriesHeader = headers.get(RETRIES_HEADER);
|
|
2191
|
+
return {
|
|
2192
|
+
flowControl: parseFlowControlHeaders(
|
|
2193
|
+
headers.get(FLOW_CONTROL_KEY_HEADER),
|
|
2194
|
+
headers.get(FLOW_CONTROL_VALUE_HEADER)
|
|
2195
|
+
),
|
|
2196
|
+
retries: retriesHeader === null ? void 0 : Number(retriesHeader),
|
|
2197
|
+
retryDelay: headers.get(RETRY_DELAY_HEADER) ?? void 0,
|
|
2198
|
+
hasStepConfig: headers.get(WORKFLOW_STEP_CONFIG_HEADER) === "true"
|
|
2199
|
+
};
|
|
2200
|
+
};
|
|
2201
|
+
var describeStepSettingsMismatch = (stepSettings, effectiveConfig) => {
|
|
2202
|
+
if (stepSettings.flowControl) {
|
|
2203
|
+
const wanted = normalizeFlowControl(stepSettings.flowControl);
|
|
2204
|
+
const applied = effectiveConfig.flowControl;
|
|
2205
|
+
if (!applied || applied.key !== wanted.key || applied.parallelism !== wanted.parallelism || applied.rate !== wanted.rate || applied.period !== wanted.period) {
|
|
2206
|
+
return `flow control: expected ${JSON.stringify(wanted)}, delivery has ${JSON.stringify(applied)}`;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
if (stepSettings.retries !== void 0 && effectiveConfig.retries !== void 0 && stepSettings.retries !== effectiveConfig.retries) {
|
|
2210
|
+
return `retries: expected ${stepSettings.retries}, delivery has ${effectiveConfig.retries}`;
|
|
2211
|
+
}
|
|
2212
|
+
if (stepSettings.retryDelay !== void 0 && stepSettings.retryDelay !== effectiveConfig.retryDelay) {
|
|
2213
|
+
return `retry delay: expected '${stepSettings.retryDelay}', delivery has '${effectiveConfig.retryDelay ?? ""}'`;
|
|
2214
|
+
}
|
|
2215
|
+
return void 0;
|
|
2216
|
+
};
|
|
1980
2217
|
|
|
1981
2218
|
// src/context/auto-executor.ts
|
|
1982
2219
|
var AutoExecutor = class _AutoExecutor {
|
|
@@ -1993,6 +2230,25 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1993
2230
|
stepCount = 0;
|
|
1994
2231
|
planStepCount = 0;
|
|
1995
2232
|
executingStep = false;
|
|
2233
|
+
/**
|
|
2234
|
+
* an executed step whose result hasn't been submitted to QStash yet.
|
|
2235
|
+
*
|
|
2236
|
+
* Set when a step which can produce its result in-process executes. The
|
|
2237
|
+
* submission waits so that the route function can continue and reveal
|
|
2238
|
+
* what comes next: if that is a single step with step-level settings,
|
|
2239
|
+
* the settings ride on this submission and no step config request is
|
|
2240
|
+
* needed, since the delivery of the submission is what executes it.
|
|
2241
|
+
*
|
|
2242
|
+
* Flushed when the next step is reached (see `addStep`) or when the
|
|
2243
|
+
* route function ends (see `flushPendingStep` in `serve`).
|
|
2244
|
+
*/
|
|
2245
|
+
pendingStep;
|
|
2246
|
+
/**
|
|
2247
|
+
* configuration QStash applied to the delivery being handled. A step
|
|
2248
|
+
* with step-level settings is settled by comparing its settings against
|
|
2249
|
+
* this.
|
|
2250
|
+
*/
|
|
2251
|
+
effectiveConfig;
|
|
1996
2252
|
/**
|
|
1997
2253
|
* @param context workflow context
|
|
1998
2254
|
* @param steps list of steps
|
|
@@ -2000,14 +2256,17 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2000
2256
|
* @param dispatchLifecycle lifecycle event dispatcher
|
|
2001
2257
|
* @param telemetry optional telemetry information
|
|
2002
2258
|
* @param invokeCount optional invoke count
|
|
2259
|
+
* @param effectiveConfig configuration QStash applied to the delivery
|
|
2260
|
+
* being handled
|
|
2003
2261
|
*/
|
|
2004
|
-
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry2, invokeCount) {
|
|
2262
|
+
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry2, invokeCount, effectiveConfig) {
|
|
2005
2263
|
this.context = context;
|
|
2006
2264
|
this.steps = steps;
|
|
2007
2265
|
this.dispatchDebug = dispatchDebug;
|
|
2008
2266
|
this.dispatchLifecycle = dispatchLifecycle;
|
|
2009
2267
|
this.telemetry = telemetry2;
|
|
2010
2268
|
this.invokeCount = invokeCount ?? 0;
|
|
2269
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2011
2270
|
this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
|
|
2012
2271
|
}
|
|
2013
2272
|
/**
|
|
@@ -2042,6 +2301,15 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2042
2301
|
lazyStepList.push(stepInfo);
|
|
2043
2302
|
const index = this.indexInCurrentList++;
|
|
2044
2303
|
const requestComplete = this.deferExecution().then(async () => {
|
|
2304
|
+
const submitted = await this.submitPendingStep(
|
|
2305
|
+
lazyStepList.length === 1 ? lazyStepList[0].stepSettings : void 0
|
|
2306
|
+
);
|
|
2307
|
+
if (submitted.isErr()) {
|
|
2308
|
+
throw submitted.error;
|
|
2309
|
+
}
|
|
2310
|
+
if (submitted.value.result === "submitted-step") {
|
|
2311
|
+
throw submitted.value.abort;
|
|
2312
|
+
}
|
|
2045
2313
|
if (!this.promises.has(lazyStepList)) {
|
|
2046
2314
|
const promise2 = this.getExecutionPromise(lazyStepList);
|
|
2047
2315
|
this.promises.set(lazyStepList, promise2);
|
|
@@ -2095,18 +2363,100 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2095
2363
|
}
|
|
2096
2364
|
return parsedOut;
|
|
2097
2365
|
}
|
|
2098
|
-
const
|
|
2099
|
-
|
|
2366
|
+
const mismatch = lazyStep.stepSettings ? describeStepSettingsMismatch(lazyStep.stepSettings, this.effectiveConfig) : void 0;
|
|
2367
|
+
if (mismatch) {
|
|
2368
|
+
if (!this.effectiveConfig.hasStepConfig) {
|
|
2369
|
+
await publishStepConfigRequest({
|
|
2370
|
+
context: this.context,
|
|
2371
|
+
lazyStep,
|
|
2372
|
+
targetStep: this.stepCount,
|
|
2373
|
+
invokeCount: this.invokeCount,
|
|
2374
|
+
telemetry: this.telemetry,
|
|
2375
|
+
dispatchDebug: this.dispatchDebug
|
|
2376
|
+
});
|
|
2377
|
+
throw new WorkflowAbort(lazyStep.stepName);
|
|
2378
|
+
} else {
|
|
2379
|
+
await this.dispatchDebug("onWarning", {
|
|
2380
|
+
warning: `The step-level settings of step '${lazyStep.stepName}' were published but are not recognized on the delivery which carries them (${mismatch}). Executing the step with the settings of the delivery instead. This is a bug in @upstash/workflow, please report it.`
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
const resultStep = await executeStep({
|
|
2100
2385
|
lazyStep,
|
|
2101
2386
|
stepId: this.stepCount,
|
|
2387
|
+
concurrency: NO_CONCURRENCY,
|
|
2388
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2389
|
+
});
|
|
2390
|
+
if (lazyStep.supportsDeferredSubmission) {
|
|
2391
|
+
this.pendingStep = { status: "held", lazyStep, resultStep };
|
|
2392
|
+
return lazyStep.parseOut({
|
|
2393
|
+
...resultStep,
|
|
2394
|
+
out: resultStep.out === void 0 ? void 0 : JSON.stringify(resultStep.out)
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
await submitStepResult({
|
|
2398
|
+
context: this.context,
|
|
2399
|
+
lazyStep,
|
|
2400
|
+
resultStep,
|
|
2102
2401
|
invokeCount: this.invokeCount,
|
|
2103
|
-
concurrency:
|
|
2402
|
+
concurrency: NO_CONCURRENCY,
|
|
2104
2403
|
telemetry: this.telemetry,
|
|
2105
|
-
dispatchDebug: this.dispatchDebug
|
|
2106
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2404
|
+
dispatchDebug: this.dispatchDebug
|
|
2107
2405
|
});
|
|
2108
2406
|
throw new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2109
2407
|
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Submits the result of the step being held, if there is one, and
|
|
2410
|
+
* returns the abort the caller has to throw to end the invocation.
|
|
2411
|
+
*
|
|
2412
|
+
* Returns undefined when no step is being held, which is the caller's
|
|
2413
|
+
* signal that there is nothing to end. Throws instead of returning
|
|
2414
|
+
* when the submission itself fails, so that error reaches the caller
|
|
2415
|
+
* rather than an abort claiming the step was submitted.
|
|
2416
|
+
*
|
|
2417
|
+
* Callers which arrive while a submission is already under way (the
|
|
2418
|
+
* steps of a parallel group, added together) wait for that one and get
|
|
2419
|
+
* the same abort, rather than submitting a second time.
|
|
2420
|
+
*
|
|
2421
|
+
* The next step's settings are attached whenever it has any, without
|
|
2422
|
+
* checking them against the current delivery: the executor only knows
|
|
2423
|
+
* the configuration of the delivery in hand, which inside a
|
|
2424
|
+
* delivery is the *previous* step's, so such a check would be wrong
|
|
2425
|
+
* exactly when two steps with settings follow each other.
|
|
2426
|
+
*
|
|
2427
|
+
* @param nextStepSettings step-level settings of the next step
|
|
2428
|
+
* @returns `submitted-step` with the abort which ends this invocation,
|
|
2429
|
+
* or `no-pending-step` when nothing was held and there is nothing to
|
|
2430
|
+
* end
|
|
2431
|
+
*/
|
|
2432
|
+
async submitPendingStep(nextStepSettings) {
|
|
2433
|
+
if (!this.pendingStep) {
|
|
2434
|
+
return ok({ result: "no-pending-step" });
|
|
2435
|
+
}
|
|
2436
|
+
try {
|
|
2437
|
+
if (this.pendingStep.status === "submitting") {
|
|
2438
|
+
return ok({ result: "submitted-step", abort: await this.pendingStep.submitted });
|
|
2439
|
+
}
|
|
2440
|
+
const { lazyStep, resultStep } = this.pendingStep;
|
|
2441
|
+
const submitted = (async () => {
|
|
2442
|
+
await submitStepResult({
|
|
2443
|
+
context: this.context,
|
|
2444
|
+
lazyStep,
|
|
2445
|
+
resultStep,
|
|
2446
|
+
invokeCount: this.invokeCount,
|
|
2447
|
+
concurrency: NO_CONCURRENCY,
|
|
2448
|
+
telemetry: this.telemetry,
|
|
2449
|
+
dispatchDebug: this.dispatchDebug,
|
|
2450
|
+
nextStepSettings
|
|
2451
|
+
});
|
|
2452
|
+
return new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2453
|
+
})();
|
|
2454
|
+
this.pendingStep = { status: "submitting", submitted };
|
|
2455
|
+
return ok({ result: "submitted-step", abort: await submitted });
|
|
2456
|
+
} catch (error) {
|
|
2457
|
+
return err(error);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2110
2460
|
/**
|
|
2111
2461
|
* Runs steps in parallel.
|
|
2112
2462
|
*
|
|
@@ -2155,15 +2505,20 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2155
2505
|
validateStep(parallelSteps[stepIndex], planStep);
|
|
2156
2506
|
try {
|
|
2157
2507
|
const parallelStep = parallelSteps[stepIndex];
|
|
2158
|
-
const resultStep = await
|
|
2159
|
-
context: this.context,
|
|
2508
|
+
const resultStep = await executeStep({
|
|
2160
2509
|
lazyStep: parallelStep,
|
|
2161
2510
|
stepId: planStep.targetStep,
|
|
2511
|
+
concurrency: parallelSteps.length,
|
|
2512
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2513
|
+
});
|
|
2514
|
+
await submitStepResult({
|
|
2515
|
+
context: this.context,
|
|
2516
|
+
lazyStep: parallelStep,
|
|
2517
|
+
resultStep,
|
|
2162
2518
|
invokeCount: this.invokeCount,
|
|
2163
2519
|
concurrency: parallelSteps.length,
|
|
2164
2520
|
telemetry: this.telemetry,
|
|
2165
|
-
dispatchDebug: this.dispatchDebug
|
|
2166
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2521
|
+
dispatchDebug: this.dispatchDebug
|
|
2167
2522
|
});
|
|
2168
2523
|
throw new WorkflowAbort(parallelStep.stepName, resultStep);
|
|
2169
2524
|
} catch (error) {
|
|
@@ -2779,6 +3134,46 @@ var WorkflowContext = class {
|
|
|
2779
3134
|
* `1` on the first retry, `2` on the second, and so on.
|
|
2780
3135
|
*/
|
|
2781
3136
|
retried;
|
|
3137
|
+
/**
|
|
3138
|
+
* Configuration QStash applied to the request being handled.
|
|
3139
|
+
*
|
|
3140
|
+
* This is the configuration of *this delivery*, not of the run: it is
|
|
3141
|
+
* the configuration the run was triggered with, except inside the
|
|
3142
|
+
* delivery which executes a step that has step-level settings, where
|
|
3143
|
+
* it is that step's settings. The fields below expose it; the executor
|
|
3144
|
+
* gets it directly rather than reaching through the context.
|
|
3145
|
+
*/
|
|
3146
|
+
effectiveConfig;
|
|
3147
|
+
/**
|
|
3148
|
+
* Flow control QStash applied to the request being handled, if any.
|
|
3149
|
+
*
|
|
3150
|
+
* This is the flow control of the request in hand, which is the one the
|
|
3151
|
+
* run was triggered with except inside a step carrying its own.
|
|
3152
|
+
*/
|
|
3153
|
+
get flowControl() {
|
|
3154
|
+
return this.effectiveConfig.flowControl;
|
|
3155
|
+
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Retry limit QStash applied to the request being handled, if it
|
|
3158
|
+
* reported one.
|
|
3159
|
+
*
|
|
3160
|
+
* Not to be confused with {@link retried}, which is how many retries
|
|
3161
|
+
* have already happened.
|
|
3162
|
+
*
|
|
3163
|
+
* @see {@link flowControl} for what "the request being handled" means
|
|
3164
|
+
*/
|
|
3165
|
+
get retries() {
|
|
3166
|
+
return this.effectiveConfig.retries;
|
|
3167
|
+
}
|
|
3168
|
+
/**
|
|
3169
|
+
* Retry delay expression QStash applied to the request being handled,
|
|
3170
|
+
* if any.
|
|
3171
|
+
*
|
|
3172
|
+
* @see {@link flowControl} for what "the request being handled" means
|
|
3173
|
+
*/
|
|
3174
|
+
get retryDelay() {
|
|
3175
|
+
return this.effectiveConfig.retryDelay;
|
|
3176
|
+
}
|
|
2782
3177
|
constructor({
|
|
2783
3178
|
qstashClient,
|
|
2784
3179
|
workflowRunId,
|
|
@@ -2792,7 +3187,8 @@ var WorkflowContext = class {
|
|
|
2792
3187
|
invokeCount,
|
|
2793
3188
|
label,
|
|
2794
3189
|
retried,
|
|
2795
|
-
middlewareManager
|
|
3190
|
+
middlewareManager,
|
|
3191
|
+
effectiveConfig
|
|
2796
3192
|
}) {
|
|
2797
3193
|
this.qstashClient = qstashClient;
|
|
2798
3194
|
this.workflowRunId = workflowRunId;
|
|
@@ -2804,6 +3200,7 @@ var WorkflowContext = class {
|
|
|
2804
3200
|
this.env = env ?? {};
|
|
2805
3201
|
this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
|
|
2806
3202
|
this.retried = retried ?? 0;
|
|
3203
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2807
3204
|
const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
|
|
2808
3205
|
middlewareManagerInstance.assignContext(this);
|
|
2809
3206
|
this.executor = new AutoExecutor(
|
|
@@ -2812,7 +3209,8 @@ var WorkflowContext = class {
|
|
|
2812
3209
|
middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
|
|
2813
3210
|
middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
|
|
2814
3211
|
telemetry2,
|
|
2815
|
-
invokeCount
|
|
3212
|
+
invokeCount,
|
|
3213
|
+
this.effectiveConfig
|
|
2816
3214
|
);
|
|
2817
3215
|
}
|
|
2818
3216
|
/**
|
|
@@ -2838,13 +3236,35 @@ var WorkflowContext = class {
|
|
|
2838
3236
|
* ])
|
|
2839
3237
|
* ```
|
|
2840
3238
|
*
|
|
3239
|
+
* Step-level settings can be passed as a third argument, overriding
|
|
3240
|
+
* the settings the workflow run was triggered with for this step only:
|
|
3241
|
+
*
|
|
3242
|
+
* ```typescript
|
|
3243
|
+
* const result = await context.run(
|
|
3244
|
+
* "step 1",
|
|
3245
|
+
* () => {
|
|
3246
|
+
* return "result"
|
|
3247
|
+
* },
|
|
3248
|
+
* {
|
|
3249
|
+
* flowControl: { key: "custom-key", parallelism: 3 },
|
|
3250
|
+
* retries: 5,
|
|
3251
|
+
* }
|
|
3252
|
+
* )
|
|
3253
|
+
* ```
|
|
3254
|
+
*
|
|
2841
3255
|
* @param stepName name of the step
|
|
2842
3256
|
* @param stepFunction step function to be executed
|
|
3257
|
+
* @param stepSettings step-level settings for this step
|
|
2843
3258
|
* @returns result of the step function
|
|
2844
3259
|
*/
|
|
2845
|
-
async run(stepName, stepFunction) {
|
|
3260
|
+
async run(stepName, stepFunction, stepSettings) {
|
|
3261
|
+
if (stepSettings) {
|
|
3262
|
+
validateFlowControl(stepSettings.flowControl);
|
|
3263
|
+
}
|
|
2846
3264
|
const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
|
|
2847
|
-
return await this.addStep(
|
|
3265
|
+
return await this.addStep(
|
|
3266
|
+
new LazyFunctionStep(this, stepName, wrappedStepFunction, stepSettings)
|
|
3267
|
+
);
|
|
2848
3268
|
}
|
|
2849
3269
|
/**
|
|
2850
3270
|
* Stops the execution for the duration provided.
|
|
@@ -3067,7 +3487,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3067
3487
|
*
|
|
3068
3488
|
* @param routeFunction
|
|
3069
3489
|
*/
|
|
3070
|
-
static async tryAuthentication(routeFunction, context) {
|
|
3490
|
+
static async tryAuthentication(routeFunction, context, effectiveConfig) {
|
|
3071
3491
|
const disabledContext = new _DisabledWorkflowContext({
|
|
3072
3492
|
qstashClient: new import_qstash10.Client({
|
|
3073
3493
|
baseUrl: "disabled-client",
|
|
@@ -3082,7 +3502,10 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3082
3502
|
initialPayload: context.requestPayload,
|
|
3083
3503
|
env: context.env,
|
|
3084
3504
|
label: context.labels,
|
|
3085
|
-
retried: context.retried
|
|
3505
|
+
retried: context.retried,
|
|
3506
|
+
// the route function runs for real here until it reaches a step, so
|
|
3507
|
+
// it observes the same configuration as it will on the real context
|
|
3508
|
+
effectiveConfig
|
|
3086
3509
|
});
|
|
3087
3510
|
try {
|
|
3088
3511
|
await routeFunction(disabledContext);
|
|
@@ -3299,6 +3722,7 @@ var handleFailure = async ({
|
|
|
3299
3722
|
}
|
|
3300
3723
|
const userHeaders = recreateUserHeaders(request.headers);
|
|
3301
3724
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3725
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3302
3726
|
const workflowContext = new WorkflowContext({
|
|
3303
3727
|
qstashClient,
|
|
3304
3728
|
workflowRunId,
|
|
@@ -3312,11 +3736,13 @@ var handleFailure = async ({
|
|
|
3312
3736
|
label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
|
|
3313
3737
|
retried,
|
|
3314
3738
|
workflowRunCreatedAt: workflowCreatedAt,
|
|
3315
|
-
middlewareManager: void 0
|
|
3739
|
+
middlewareManager: void 0,
|
|
3740
|
+
effectiveConfig
|
|
3316
3741
|
});
|
|
3317
3742
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3318
3743
|
routeFunction,
|
|
3319
|
-
workflowContext
|
|
3744
|
+
workflowContext,
|
|
3745
|
+
effectiveConfig
|
|
3320
3746
|
);
|
|
3321
3747
|
if (authCheck.isErr()) {
|
|
3322
3748
|
await dispatchDebug?.("onError", {
|
|
@@ -3408,7 +3834,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
|
|
|
3408
3834
|
const client = new import_qstash11.Client({
|
|
3409
3835
|
...clientOptions,
|
|
3410
3836
|
baseUrl: clientEnv.QSTASH_URL,
|
|
3411
|
-
token: clientEnv.QSTASH_TOKEN
|
|
3837
|
+
token: clientEnv.QSTASH_TOKEN,
|
|
3838
|
+
// Mirror the receiver's dev-mode decision so a single QSTASH_DEV=true points
|
|
3839
|
+
// the client at the local dev server too. Without this the QStash Client
|
|
3840
|
+
// re-derives dev mode from its own process.env, which is blind to the
|
|
3841
|
+
// Cloudflare worker `env` binding that actually carries QSTASH_DEV there.
|
|
3842
|
+
// An explicit devMode in the user's client config still wins.
|
|
3843
|
+
devMode: clientOptions?.devMode ?? isQStashDevModeEnabled(environment)
|
|
3412
3844
|
});
|
|
3413
3845
|
const receiver = getReceiver(environment, receiverConfig, region);
|
|
3414
3846
|
return { client, receiver };
|
|
@@ -3458,7 +3890,9 @@ var getQStashHandlers = ({
|
|
|
3458
3890
|
client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
|
|
3459
3891
|
...qstashClientOption,
|
|
3460
3892
|
baseUrl: environment.QSTASH_URL,
|
|
3461
|
-
token: environment.QSTASH_TOKEN
|
|
3893
|
+
token: environment.QSTASH_TOKEN,
|
|
3894
|
+
// Mirror the receiver's dev-mode decision (see createRegionalHandler).
|
|
3895
|
+
devMode: qstashClientOption?.devMode ?? isQStashDevModeEnabled(environment)
|
|
3462
3896
|
}),
|
|
3463
3897
|
receiver: getReceiver(environment, receiverConfig)
|
|
3464
3898
|
}
|
|
@@ -3798,12 +4232,14 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
3798
4232
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3799
4233
|
const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
|
|
3800
4234
|
const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
4235
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3801
4236
|
const workflowContext = new WorkflowContext({
|
|
3802
4237
|
qstashClient: regionalClient,
|
|
3803
4238
|
workflowRunId,
|
|
3804
4239
|
initialPayload: isThirdPartyCallResult(request) ? JSON.parse(rawInitialPayload) : initialPayloadParser(rawInitialPayload),
|
|
3805
4240
|
headers: recreateUserHeaders(request.headers),
|
|
3806
4241
|
steps,
|
|
4242
|
+
effectiveConfig,
|
|
3807
4243
|
url: workflowUrl,
|
|
3808
4244
|
env,
|
|
3809
4245
|
telemetry: telemetry2,
|
|
@@ -3815,7 +4251,8 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
3815
4251
|
});
|
|
3816
4252
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3817
4253
|
routeFunction,
|
|
3818
|
-
workflowContext
|
|
4254
|
+
workflowContext,
|
|
4255
|
+
effectiveConfig
|
|
3819
4256
|
);
|
|
3820
4257
|
if (authCheck.isErr()) {
|
|
3821
4258
|
throw authCheck.error;
|
|
@@ -3852,7 +4289,23 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
3852
4289
|
if (steps.length === 1) {
|
|
3853
4290
|
await middlewareManager.dispatchLifecycle("runStarted", {});
|
|
3854
4291
|
}
|
|
3855
|
-
|
|
4292
|
+
let outcome;
|
|
4293
|
+
try {
|
|
4294
|
+
outcome = { ran: true, result: await routeFunction(workflowContext) };
|
|
4295
|
+
} catch (error) {
|
|
4296
|
+
outcome = { ran: false, error };
|
|
4297
|
+
}
|
|
4298
|
+
const submitted = await flushPendingStep(workflowContext);
|
|
4299
|
+
if (submitted.isErr()) {
|
|
4300
|
+
throw submitted.error;
|
|
4301
|
+
}
|
|
4302
|
+
if (submitted.value.result === "submitted-step") {
|
|
4303
|
+
throw submitted.value.abort;
|
|
4304
|
+
}
|
|
4305
|
+
if (!outcome.ran) {
|
|
4306
|
+
throw outcome.error;
|
|
4307
|
+
}
|
|
4308
|
+
return outcome.result;
|
|
3856
4309
|
},
|
|
3857
4310
|
onCleanup: async (result2) => {
|
|
3858
4311
|
await middlewareManager.dispatchLifecycle("runCompleted", {
|
|
@@ -3933,12 +4386,14 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
3933
4386
|
};
|
|
3934
4387
|
|
|
3935
4388
|
// platforms/astro.ts
|
|
4389
|
+
var import_qstash13 = require("@upstash/qstash");
|
|
3936
4390
|
var telemetry = {
|
|
3937
4391
|
sdk: SDK_TELEMETRY,
|
|
3938
4392
|
framework: "astro",
|
|
3939
4393
|
runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
|
|
3940
4394
|
};
|
|
3941
4395
|
function serve(routeFunction, options) {
|
|
4396
|
+
void (0, import_qstash13.startDevServer)();
|
|
3942
4397
|
const POST = (apiContext) => {
|
|
3943
4398
|
const { handler } = serveBase(
|
|
3944
4399
|
(workflowContext) => routeFunction(workflowContext, apiContext),
|