@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/solidjs.js
CHANGED
|
@@ -24,6 +24,9 @@ __export(solidjs_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(solidjs_exports);
|
|
26
26
|
|
|
27
|
+
// src/serve/index.ts
|
|
28
|
+
var import_qstash12 = require("@upstash/qstash");
|
|
29
|
+
|
|
27
30
|
// src/client/utils.ts
|
|
28
31
|
var import_qstash2 = require("@upstash/qstash");
|
|
29
32
|
|
|
@@ -140,8 +143,15 @@ function isInstanceOf(v, ctor) {
|
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
// src/client/utils.ts
|
|
146
|
+
var assertNonEmptyId = (id, label = "id") => {
|
|
147
|
+
if (!id) {
|
|
148
|
+
throw new import_qstash2.QstashError(`${label} cannot be empty`);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
143
151
|
var makeNotifyRequest = async (requester, eventId, eventData, workflowRunId) => {
|
|
144
|
-
|
|
152
|
+
assertNonEmptyId(eventId, "Event id");
|
|
153
|
+
if (workflowRunId !== void 0) assertNonEmptyId(workflowRunId, "Workflow run id");
|
|
154
|
+
const path = workflowRunId === void 0 ? ["v2", "notify", eventId] : ["v2", "notify", workflowRunId, eventId];
|
|
145
155
|
const result = await requester.request({
|
|
146
156
|
path,
|
|
147
157
|
method: "POST",
|
|
@@ -205,6 +215,14 @@ var WORKFLOW_CREATED_AT_HEADER = "Upstash-Workflow-CreatedAt";
|
|
|
205
215
|
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
|
|
206
216
|
var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
|
|
207
217
|
var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
|
|
218
|
+
var WORKFLOW_FEATURE_SET = "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig";
|
|
219
|
+
var WORKFLOW_STEP_CONFIG_CALL_TYPE = "stepConfig";
|
|
220
|
+
var WORKFLOW_STEP_CONFIG_HEADER = "Upstash-Workflow-Step-Config";
|
|
221
|
+
var FLOW_CONTROL_KEY_HEADER = "Upstash-Flow-Control-Key";
|
|
222
|
+
var FLOW_CONTROL_VALUE_HEADER = "Upstash-Flow-Control-Value";
|
|
223
|
+
var RETRIES_HEADER = "Upstash-Retries";
|
|
224
|
+
var RETRY_DELAY_HEADER = "Upstash-Retry-Delay";
|
|
225
|
+
var WORKFLOW_STEP_CONFIG_FEATURE = "WF_StepConfig";
|
|
208
226
|
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
|
|
209
227
|
var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
|
|
210
228
|
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
|
|
@@ -222,102 +240,6 @@ var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
|
|
|
222
240
|
var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
|
|
223
241
|
var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
|
|
224
242
|
|
|
225
|
-
// src/context/auto-executor.ts
|
|
226
|
-
var import_qstash5 = require("@upstash/qstash");
|
|
227
|
-
|
|
228
|
-
// src/qstash/headers.ts
|
|
229
|
-
var import_qstash4 = require("@upstash/qstash");
|
|
230
|
-
|
|
231
|
-
// src/utils.ts
|
|
232
|
-
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
233
|
-
var NANOID_LENGTH = 21;
|
|
234
|
-
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
235
|
-
function validateLabel(label) {
|
|
236
|
-
if (label === void 0) return;
|
|
237
|
-
const labels = Array.isArray(label) ? label : [label];
|
|
238
|
-
if (labels.length === 0) {
|
|
239
|
-
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
240
|
-
}
|
|
241
|
-
for (const value of labels) {
|
|
242
|
-
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
243
|
-
throw new WorkflowNonRetryableError(
|
|
244
|
-
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
function serializeLabel(label) {
|
|
250
|
-
return Array.isArray(label) ? label.join(",") : label;
|
|
251
|
-
}
|
|
252
|
-
function validateFlowControl(flowControl) {
|
|
253
|
-
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
254
|
-
throw new WorkflowNonRetryableError(
|
|
255
|
-
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
function getRandomInt() {
|
|
260
|
-
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
261
|
-
}
|
|
262
|
-
function nanoid(length = NANOID_LENGTH) {
|
|
263
|
-
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
264
|
-
}
|
|
265
|
-
function getWorkflowRunId(id) {
|
|
266
|
-
return `wfr_${id ?? nanoid()}`;
|
|
267
|
-
}
|
|
268
|
-
function decodeBase64(base64) {
|
|
269
|
-
const binString = atob(base64);
|
|
270
|
-
try {
|
|
271
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
272
|
-
return new TextDecoder().decode(intArray);
|
|
273
|
-
} catch (error) {
|
|
274
|
-
console.warn(
|
|
275
|
-
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
276
|
-
);
|
|
277
|
-
return binString;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
function getUserIdFromToken(qstashClient) {
|
|
281
|
-
try {
|
|
282
|
-
const token = qstashClient.token;
|
|
283
|
-
const decodedToken = decodeBase64(token);
|
|
284
|
-
const tokenPayload = JSON.parse(decodedToken);
|
|
285
|
-
const userId = tokenPayload.UserID;
|
|
286
|
-
if (!userId) {
|
|
287
|
-
throw new WorkflowError("QStash token payload does not contain userId");
|
|
288
|
-
}
|
|
289
|
-
return userId;
|
|
290
|
-
} catch (error) {
|
|
291
|
-
throw new WorkflowError(
|
|
292
|
-
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
function getQStashUrl(qstashClient) {
|
|
297
|
-
try {
|
|
298
|
-
const requester = qstashClient.http;
|
|
299
|
-
const baseUrl = requester.baseUrl;
|
|
300
|
-
if (!baseUrl) {
|
|
301
|
-
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
302
|
-
}
|
|
303
|
-
return baseUrl;
|
|
304
|
-
} catch (error) {
|
|
305
|
-
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
function getEventId() {
|
|
309
|
-
return `evt_${nanoid(15)}`;
|
|
310
|
-
}
|
|
311
|
-
function stringifyBody(body) {
|
|
312
|
-
if (body === void 0) {
|
|
313
|
-
return void 0;
|
|
314
|
-
}
|
|
315
|
-
if (typeof body === "string") {
|
|
316
|
-
return body;
|
|
317
|
-
}
|
|
318
|
-
return JSON.stringify(body);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
243
|
// node_modules/neverthrow/dist/index.es.js
|
|
322
244
|
var defaultErrorConfig = {
|
|
323
245
|
withStackTrace: false
|
|
@@ -733,6 +655,102 @@ var Err = class {
|
|
|
733
655
|
};
|
|
734
656
|
var fromThrowable = Result.fromThrowable;
|
|
735
657
|
|
|
658
|
+
// src/context/auto-executor.ts
|
|
659
|
+
var import_qstash5 = require("@upstash/qstash");
|
|
660
|
+
|
|
661
|
+
// src/qstash/headers.ts
|
|
662
|
+
var import_qstash4 = require("@upstash/qstash");
|
|
663
|
+
|
|
664
|
+
// src/utils.ts
|
|
665
|
+
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
666
|
+
var NANOID_LENGTH = 21;
|
|
667
|
+
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
668
|
+
function validateLabel(label) {
|
|
669
|
+
if (label === void 0) return;
|
|
670
|
+
const labels = Array.isArray(label) ? label : [label];
|
|
671
|
+
if (labels.length === 0) {
|
|
672
|
+
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
673
|
+
}
|
|
674
|
+
for (const value of labels) {
|
|
675
|
+
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
676
|
+
throw new WorkflowNonRetryableError(
|
|
677
|
+
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function serializeLabel(label) {
|
|
683
|
+
return Array.isArray(label) ? label.join(",") : label;
|
|
684
|
+
}
|
|
685
|
+
function validateFlowControl(flowControl) {
|
|
686
|
+
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
687
|
+
throw new WorkflowNonRetryableError(
|
|
688
|
+
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function getRandomInt() {
|
|
693
|
+
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
694
|
+
}
|
|
695
|
+
function nanoid(length = NANOID_LENGTH) {
|
|
696
|
+
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
697
|
+
}
|
|
698
|
+
function getWorkflowRunId(id) {
|
|
699
|
+
return `wfr_${id ?? nanoid()}`;
|
|
700
|
+
}
|
|
701
|
+
function decodeBase64(base64) {
|
|
702
|
+
const binString = atob(base64);
|
|
703
|
+
try {
|
|
704
|
+
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
705
|
+
return new TextDecoder().decode(intArray);
|
|
706
|
+
} catch (error) {
|
|
707
|
+
console.warn(
|
|
708
|
+
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
709
|
+
);
|
|
710
|
+
return binString;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function getUserIdFromToken(qstashClient) {
|
|
714
|
+
try {
|
|
715
|
+
const token = qstashClient.token;
|
|
716
|
+
const decodedToken = decodeBase64(token);
|
|
717
|
+
const tokenPayload = JSON.parse(decodedToken);
|
|
718
|
+
const userId = tokenPayload.UserID;
|
|
719
|
+
if (!userId) {
|
|
720
|
+
throw new WorkflowError("QStash token payload does not contain userId");
|
|
721
|
+
}
|
|
722
|
+
return userId;
|
|
723
|
+
} catch (error) {
|
|
724
|
+
throw new WorkflowError(
|
|
725
|
+
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
function getQStashUrl(qstashClient) {
|
|
730
|
+
try {
|
|
731
|
+
const requester = qstashClient.http;
|
|
732
|
+
const baseUrl = requester.baseUrl;
|
|
733
|
+
if (!baseUrl) {
|
|
734
|
+
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
735
|
+
}
|
|
736
|
+
return baseUrl;
|
|
737
|
+
} catch (error) {
|
|
738
|
+
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function getEventId() {
|
|
742
|
+
return `evt_${nanoid(15)}`;
|
|
743
|
+
}
|
|
744
|
+
function stringifyBody(body) {
|
|
745
|
+
if (body === void 0) {
|
|
746
|
+
return void 0;
|
|
747
|
+
}
|
|
748
|
+
if (typeof body === "string") {
|
|
749
|
+
return body;
|
|
750
|
+
}
|
|
751
|
+
return JSON.stringify(body);
|
|
752
|
+
}
|
|
753
|
+
|
|
736
754
|
// src/types.ts
|
|
737
755
|
var StepTypes = [
|
|
738
756
|
"Initial",
|
|
@@ -838,6 +856,10 @@ var triggerFirstInvocation = async (params) => {
|
|
|
838
856
|
return err(error_);
|
|
839
857
|
}
|
|
840
858
|
};
|
|
859
|
+
var flushPendingStep = async (workflowContext) => {
|
|
860
|
+
const { executor } = workflowContext;
|
|
861
|
+
return await executor.submitPendingStep();
|
|
862
|
+
};
|
|
841
863
|
var triggerRouteFunction = async ({
|
|
842
864
|
onCleanup,
|
|
843
865
|
onStep,
|
|
@@ -1045,8 +1067,29 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
|
|
|
1045
1067
|
var BaseLazyStep = class _BaseLazyStep {
|
|
1046
1068
|
stepName;
|
|
1047
1069
|
context;
|
|
1048
|
-
|
|
1070
|
+
/**
|
|
1071
|
+
* step-level settings (flow control, retries etc.) which override the
|
|
1072
|
+
* settings the workflow run was triggered with, for this step only.
|
|
1073
|
+
*/
|
|
1074
|
+
stepSettings;
|
|
1075
|
+
/**
|
|
1076
|
+
* whether this step's result can be submitted after the route function
|
|
1077
|
+
* has moved on, instead of right away.
|
|
1078
|
+
*
|
|
1079
|
+
* Enabled for steps whose `getResultStep` produces the final `out` —
|
|
1080
|
+
* which is not the same as "needs no server round trip":
|
|
1081
|
+
* `LazyCreateWebhookStep` builds its result in `getBody` and returns
|
|
1082
|
+
* `out: undefined` from `getResultStep`, so deferring it would hand
|
|
1083
|
+
* `undefined` to the route function.
|
|
1084
|
+
*
|
|
1085
|
+
* Deferring lets the route function continue and reveal the next step,
|
|
1086
|
+
* so that step's settings can ride on this step's submission instead
|
|
1087
|
+
* of needing a step config request of their own.
|
|
1088
|
+
*/
|
|
1089
|
+
supportsDeferredSubmission = false;
|
|
1090
|
+
constructor(context, stepName, stepSettings) {
|
|
1049
1091
|
this.context = context;
|
|
1092
|
+
this.stepSettings = stepSettings;
|
|
1050
1093
|
if (!stepName) {
|
|
1051
1094
|
throw new WorkflowError(
|
|
1052
1095
|
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
|
|
@@ -1110,7 +1153,13 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1110
1153
|
step.out = JSON.stringify(step.out);
|
|
1111
1154
|
return JSON.stringify(step);
|
|
1112
1155
|
}
|
|
1113
|
-
getHeaders({
|
|
1156
|
+
getHeaders({
|
|
1157
|
+
context,
|
|
1158
|
+
telemetry,
|
|
1159
|
+
invokeCount,
|
|
1160
|
+
step,
|
|
1161
|
+
stepSettings
|
|
1162
|
+
}) {
|
|
1114
1163
|
return getHeaders({
|
|
1115
1164
|
initHeaderValue: "false",
|
|
1116
1165
|
workflowConfig: {
|
|
@@ -1123,7 +1172,8 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1123
1172
|
stepInfo: {
|
|
1124
1173
|
step,
|
|
1125
1174
|
lazyStep: this
|
|
1126
|
-
}
|
|
1175
|
+
},
|
|
1176
|
+
stepSettings
|
|
1127
1177
|
});
|
|
1128
1178
|
}
|
|
1129
1179
|
async submitStep({ context, body, headers }) {
|
|
@@ -1141,8 +1191,9 @@ var LazyFunctionStep = class extends BaseLazyStep {
|
|
|
1141
1191
|
stepFunction;
|
|
1142
1192
|
stepType = "Run";
|
|
1143
1193
|
allowUndefinedOut = true;
|
|
1144
|
-
|
|
1145
|
-
|
|
1194
|
+
supportsDeferredSubmission = true;
|
|
1195
|
+
constructor(context, stepName, stepFunction, stepSettings) {
|
|
1196
|
+
super(context, stepName, stepSettings);
|
|
1146
1197
|
this.stepFunction = stepFunction;
|
|
1147
1198
|
}
|
|
1148
1199
|
getPlanStep(concurrent, targetStep) {
|
|
@@ -1172,6 +1223,7 @@ var LazySleepStep = class extends BaseLazyStep {
|
|
|
1172
1223
|
sleep;
|
|
1173
1224
|
stepType = "SleepFor";
|
|
1174
1225
|
allowUndefinedOut = true;
|
|
1226
|
+
supportsDeferredSubmission = true;
|
|
1175
1227
|
constructor(context, stepName, sleep) {
|
|
1176
1228
|
super(context, stepName);
|
|
1177
1229
|
this.sleep = sleep;
|
|
@@ -1211,6 +1263,7 @@ var LazySleepUntilStep = class extends BaseLazyStep {
|
|
|
1211
1263
|
sleepUntil;
|
|
1212
1264
|
stepType = "SleepUntil";
|
|
1213
1265
|
allowUndefinedOut = true;
|
|
1266
|
+
supportsDeferredSubmission = true;
|
|
1214
1267
|
constructor(context, stepName, sleepUntil) {
|
|
1215
1268
|
super(context, stepName);
|
|
1216
1269
|
this.sleepUntil = sleepUntil;
|
|
@@ -1703,6 +1756,7 @@ var WorkflowHeaders = class {
|
|
|
1703
1756
|
invokeCount;
|
|
1704
1757
|
initHeaderValue;
|
|
1705
1758
|
stepInfo;
|
|
1759
|
+
stepSettings;
|
|
1706
1760
|
headers;
|
|
1707
1761
|
/**
|
|
1708
1762
|
* @param params workflow header parameters
|
|
@@ -1712,17 +1766,20 @@ var WorkflowHeaders = class {
|
|
|
1712
1766
|
workflowConfig,
|
|
1713
1767
|
invokeCount,
|
|
1714
1768
|
initHeaderValue,
|
|
1715
|
-
stepInfo
|
|
1769
|
+
stepInfo,
|
|
1770
|
+
stepSettings
|
|
1716
1771
|
}) {
|
|
1717
1772
|
this.userHeaders = userHeaders;
|
|
1718
1773
|
this.workflowConfig = workflowConfig;
|
|
1719
1774
|
this.invokeCount = invokeCount;
|
|
1720
1775
|
this.initHeaderValue = initHeaderValue;
|
|
1721
1776
|
this.stepInfo = stepInfo;
|
|
1777
|
+
this.stepSettings = stepSettings;
|
|
1722
1778
|
this.headers = {
|
|
1723
1779
|
rawHeaders: {},
|
|
1724
1780
|
workflowHeaders: {},
|
|
1725
|
-
failureHeaders: {}
|
|
1781
|
+
failureHeaders: {},
|
|
1782
|
+
stepSettingsHeaders: {}
|
|
1726
1783
|
};
|
|
1727
1784
|
}
|
|
1728
1785
|
getHeaders() {
|
|
@@ -1733,6 +1790,7 @@ var WorkflowHeaders = class {
|
|
|
1733
1790
|
this.addUserHeaders();
|
|
1734
1791
|
this.addInvokeCount();
|
|
1735
1792
|
this.addFailureUrl();
|
|
1793
|
+
this.addStepSettings();
|
|
1736
1794
|
const contentType = this.addContentType();
|
|
1737
1795
|
return this.prefixHeaders(contentType);
|
|
1738
1796
|
}
|
|
@@ -1742,7 +1800,7 @@ var WorkflowHeaders = class {
|
|
|
1742
1800
|
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
|
|
1743
1801
|
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
|
|
1744
1802
|
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
|
|
1745
|
-
[WORKFLOW_FEATURE_HEADER]:
|
|
1803
|
+
[WORKFLOW_FEATURE_HEADER]: WORKFLOW_FEATURE_SET,
|
|
1746
1804
|
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
1747
1805
|
...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
|
|
1748
1806
|
};
|
|
@@ -1820,7 +1878,7 @@ var WorkflowHeaders = class {
|
|
|
1820
1878
|
this.headers.failureHeaders["Workflow-Init"] = "false";
|
|
1821
1879
|
this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
|
|
1822
1880
|
this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
|
|
1823
|
-
this.headers.failureHeaders["Feature-Set"] =
|
|
1881
|
+
this.headers.failureHeaders["Feature-Set"] = WORKFLOW_FEATURE_SET;
|
|
1824
1882
|
if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
|
|
1825
1883
|
this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
|
|
1826
1884
|
}
|
|
@@ -1828,6 +1886,42 @@ var WorkflowHeaders = class {
|
|
|
1828
1886
|
this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
|
|
1829
1887
|
}
|
|
1830
1888
|
}
|
|
1889
|
+
/**
|
|
1890
|
+
* Applies the step-level settings of a step to the message.
|
|
1891
|
+
*
|
|
1892
|
+
* These headers configure the message itself, so that QStash uses them
|
|
1893
|
+
* instead of the settings the run was triggered with. The delivery of
|
|
1894
|
+
* the message is what executes the step, which is how a step's
|
|
1895
|
+
* settings reach it.
|
|
1896
|
+
*
|
|
1897
|
+
* When any setting is present the feature set is extended with
|
|
1898
|
+
* `WF_StepConfig`, which is what tells QStash to keep them. QStash
|
|
1899
|
+
* reports that back on the delivery as `Upstash-Workflow-Step-Config`.
|
|
1900
|
+
*/
|
|
1901
|
+
addStepSettings() {
|
|
1902
|
+
if (!this.stepSettings) {
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
const headers = {};
|
|
1906
|
+
if (this.stepSettings.flowControl) {
|
|
1907
|
+
const { flowControlKey, flowControlValue } = prepareFlowControl(
|
|
1908
|
+
this.stepSettings.flowControl
|
|
1909
|
+
);
|
|
1910
|
+
headers[FLOW_CONTROL_KEY_HEADER] = flowControlKey;
|
|
1911
|
+
headers[FLOW_CONTROL_VALUE_HEADER] = flowControlValue;
|
|
1912
|
+
}
|
|
1913
|
+
if (this.stepSettings.retries !== void 0) {
|
|
1914
|
+
headers[RETRIES_HEADER] = this.stepSettings.retries.toString();
|
|
1915
|
+
}
|
|
1916
|
+
if (this.stepSettings.retryDelay) {
|
|
1917
|
+
headers[RETRY_DELAY_HEADER] = this.stepSettings.retryDelay;
|
|
1918
|
+
}
|
|
1919
|
+
if (Object.keys(headers).length === 0) {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
headers[WORKFLOW_FEATURE_HEADER] = `${WORKFLOW_FEATURE_SET},${WORKFLOW_STEP_CONFIG_FEATURE}`;
|
|
1923
|
+
this.headers.stepSettingsHeaders = headers;
|
|
1924
|
+
}
|
|
1831
1925
|
addContentType() {
|
|
1832
1926
|
if (this.workflowConfig.useJSONContent) {
|
|
1833
1927
|
this.headers.rawHeaders["content-type"] = "application/json";
|
|
@@ -1841,14 +1935,17 @@ var WorkflowHeaders = class {
|
|
|
1841
1935
|
return contentType;
|
|
1842
1936
|
}
|
|
1843
1937
|
prefixHeaders(contentType) {
|
|
1844
|
-
const { rawHeaders, workflowHeaders, failureHeaders } = this.headers;
|
|
1938
|
+
const { rawHeaders, workflowHeaders, failureHeaders, stepSettingsHeaders } = this.headers;
|
|
1845
1939
|
const isCall = this.stepInfo?.lazyStep.stepType === "Call";
|
|
1846
1940
|
return {
|
|
1847
1941
|
headers: {
|
|
1848
1942
|
...rawHeaders,
|
|
1849
1943
|
...addPrefixToHeaders(workflowHeaders, isCall ? "Upstash-Callback-" : "Upstash-"),
|
|
1850
1944
|
...addPrefixToHeaders(failureHeaders, "Upstash-Failure-Callback-"),
|
|
1851
|
-
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {}
|
|
1945
|
+
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {},
|
|
1946
|
+
// last, so they override the run's settings above. Never
|
|
1947
|
+
// prefixed: they configure this message, not its callback.
|
|
1948
|
+
...stepSettingsHeaders
|
|
1852
1949
|
},
|
|
1853
1950
|
contentType
|
|
1854
1951
|
};
|
|
@@ -1899,7 +1996,7 @@ var submitParallelSteps = async ({
|
|
|
1899
1996
|
info: `Submitting ${planSteps.length} parallel steps.`
|
|
1900
1997
|
});
|
|
1901
1998
|
const result = await context.qstashClient.batch(
|
|
1902
|
-
planSteps.map((planStep) => {
|
|
1999
|
+
planSteps.map((planStep, index) => {
|
|
1903
2000
|
const { headers } = getHeaders({
|
|
1904
2001
|
initHeaderValue: "false",
|
|
1905
2002
|
workflowConfig: {
|
|
@@ -1907,7 +2004,8 @@ var submitParallelSteps = async ({
|
|
|
1907
2004
|
workflowUrl: context.url,
|
|
1908
2005
|
telemetry
|
|
1909
2006
|
},
|
|
1910
|
-
invokeCount
|
|
2007
|
+
invokeCount,
|
|
2008
|
+
stepSettings: steps[index].stepSettings
|
|
1911
2009
|
});
|
|
1912
2010
|
return {
|
|
1913
2011
|
headers,
|
|
@@ -1926,31 +2024,38 @@ var submitParallelSteps = async ({
|
|
|
1926
2024
|
}
|
|
1927
2025
|
throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
|
|
1928
2026
|
};
|
|
1929
|
-
var
|
|
1930
|
-
context,
|
|
2027
|
+
var executeStep = async ({
|
|
1931
2028
|
lazyStep,
|
|
1932
2029
|
stepId,
|
|
1933
|
-
invokeCount,
|
|
1934
2030
|
concurrency,
|
|
1935
|
-
telemetry,
|
|
1936
|
-
dispatchDebug,
|
|
1937
2031
|
dispatchLifecycle
|
|
1938
2032
|
}) => {
|
|
1939
2033
|
await dispatchLifecycle("beforeExecution", {
|
|
1940
2034
|
stepName: lazyStep.stepName
|
|
1941
2035
|
});
|
|
1942
|
-
let resultStep;
|
|
1943
2036
|
try {
|
|
1944
|
-
|
|
2037
|
+
return await lazyStep.getResultStep(concurrency, stepId);
|
|
1945
2038
|
} catch (error) {
|
|
1946
2039
|
attachStepNameToError(error, lazyStep.stepName);
|
|
1947
2040
|
throw error;
|
|
1948
2041
|
}
|
|
2042
|
+
};
|
|
2043
|
+
var submitStepResult = async ({
|
|
2044
|
+
context,
|
|
2045
|
+
lazyStep,
|
|
2046
|
+
resultStep,
|
|
2047
|
+
invokeCount,
|
|
2048
|
+
concurrency,
|
|
2049
|
+
telemetry,
|
|
2050
|
+
dispatchDebug,
|
|
2051
|
+
nextStepSettings
|
|
2052
|
+
}) => {
|
|
1949
2053
|
const { headers } = lazyStep.getHeaders({
|
|
1950
2054
|
context,
|
|
1951
2055
|
step: resultStep,
|
|
1952
2056
|
invokeCount,
|
|
1953
|
-
telemetry
|
|
2057
|
+
telemetry,
|
|
2058
|
+
stepSettings: nextStepSettings
|
|
1954
2059
|
});
|
|
1955
2060
|
const body = lazyStep.getBody({
|
|
1956
2061
|
context,
|
|
@@ -1975,6 +2080,138 @@ var submitSingleStep = async ({
|
|
|
1975
2080
|
}
|
|
1976
2081
|
return resultStep;
|
|
1977
2082
|
};
|
|
2083
|
+
var publishStepConfigRequest = async ({
|
|
2084
|
+
context,
|
|
2085
|
+
lazyStep,
|
|
2086
|
+
targetStep,
|
|
2087
|
+
invokeCount,
|
|
2088
|
+
telemetry,
|
|
2089
|
+
dispatchDebug
|
|
2090
|
+
}) => {
|
|
2091
|
+
const { headers } = getHeaders({
|
|
2092
|
+
initHeaderValue: "false",
|
|
2093
|
+
workflowConfig: {
|
|
2094
|
+
workflowRunId: context.workflowRunId,
|
|
2095
|
+
workflowUrl: context.url,
|
|
2096
|
+
telemetry
|
|
2097
|
+
},
|
|
2098
|
+
invokeCount,
|
|
2099
|
+
stepSettings: lazyStep.stepSettings
|
|
2100
|
+
});
|
|
2101
|
+
await dispatchDebug("onInfo", {
|
|
2102
|
+
info: `Step "${lazyStep.stepName}" (${targetStep}) has step-level settings which the current delivery does not carry. Requesting a delivery which does.`
|
|
2103
|
+
});
|
|
2104
|
+
const result = await context.qstashClient.publishJSON({
|
|
2105
|
+
headers: {
|
|
2106
|
+
...headers,
|
|
2107
|
+
"Upstash-Workflow-CallType": WORKFLOW_STEP_CONFIG_CALL_TYPE
|
|
2108
|
+
},
|
|
2109
|
+
method: "POST",
|
|
2110
|
+
body: { targetStep, invokeCount },
|
|
2111
|
+
url: context.url,
|
|
2112
|
+
contentBasedDeduplication: true
|
|
2113
|
+
});
|
|
2114
|
+
if (result?.deduplicated) {
|
|
2115
|
+
await dispatchDebug("onWarning", {
|
|
2116
|
+
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.`
|
|
2117
|
+
});
|
|
2118
|
+
} else if (result?.messageId) {
|
|
2119
|
+
await dispatchDebug("onInfo", {
|
|
2120
|
+
info: `Requested a delivery under the settings of step "${lazyStep.stepName}" with messageId: ${result.messageId}.`
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
|
|
2125
|
+
// src/qstash/step-config.ts
|
|
2126
|
+
var DEFAULT_PARALLELISM = 0;
|
|
2127
|
+
var DEFAULT_RATE = 0;
|
|
2128
|
+
var DEFAULT_PERIOD_SECONDS = 1;
|
|
2129
|
+
var SECONDS_PER_UNIT = {
|
|
2130
|
+
s: 1,
|
|
2131
|
+
m: 60,
|
|
2132
|
+
h: 60 * 60,
|
|
2133
|
+
d: 24 * 60 * 60
|
|
2134
|
+
};
|
|
2135
|
+
var durationToSeconds = (duration) => {
|
|
2136
|
+
if (typeof duration === "number") {
|
|
2137
|
+
return Number.isFinite(duration) ? duration : void 0;
|
|
2138
|
+
}
|
|
2139
|
+
const match = /^(\d+)([smhd])$/.exec(duration.trim());
|
|
2140
|
+
if (match) {
|
|
2141
|
+
return Number(match[1]) * SECONDS_PER_UNIT[match[2]];
|
|
2142
|
+
}
|
|
2143
|
+
const seconds = Number(duration.trim());
|
|
2144
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2145
|
+
};
|
|
2146
|
+
var normalizeFlowControl = (flowControl) => {
|
|
2147
|
+
const period = flowControl.period === void 0 ? void 0 : durationToSeconds(flowControl.period);
|
|
2148
|
+
return {
|
|
2149
|
+
key: flowControl.key,
|
|
2150
|
+
parallelism: flowControl.parallelism ?? DEFAULT_PARALLELISM,
|
|
2151
|
+
rate: flowControl.rate ?? flowControl.ratePerSecond ?? DEFAULT_RATE,
|
|
2152
|
+
period: period ?? DEFAULT_PERIOD_SECONDS
|
|
2153
|
+
};
|
|
2154
|
+
};
|
|
2155
|
+
var parseFlowControlHeaders = (key, value) => {
|
|
2156
|
+
if (!key) {
|
|
2157
|
+
return void 0;
|
|
2158
|
+
}
|
|
2159
|
+
const flowControl = {
|
|
2160
|
+
key,
|
|
2161
|
+
parallelism: DEFAULT_PARALLELISM,
|
|
2162
|
+
rate: DEFAULT_RATE,
|
|
2163
|
+
period: DEFAULT_PERIOD_SECONDS
|
|
2164
|
+
};
|
|
2165
|
+
for (const entry of (value ?? "").split(",")) {
|
|
2166
|
+
const [name, rawValue] = entry.split("=").map((part) => part.trim());
|
|
2167
|
+
if (!name || rawValue === void 0) {
|
|
2168
|
+
continue;
|
|
2169
|
+
}
|
|
2170
|
+
switch (name) {
|
|
2171
|
+
case "parallelism": {
|
|
2172
|
+
flowControl.parallelism = Number(rawValue);
|
|
2173
|
+
break;
|
|
2174
|
+
}
|
|
2175
|
+
case "rate": {
|
|
2176
|
+
flowControl.rate = Number(rawValue);
|
|
2177
|
+
break;
|
|
2178
|
+
}
|
|
2179
|
+
case "period": {
|
|
2180
|
+
flowControl.period = durationToSeconds(rawValue) ?? DEFAULT_PERIOD_SECONDS;
|
|
2181
|
+
break;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
return flowControl;
|
|
2186
|
+
};
|
|
2187
|
+
var getEffectiveConfig = (headers) => {
|
|
2188
|
+
const retriesHeader = headers.get(RETRIES_HEADER);
|
|
2189
|
+
return {
|
|
2190
|
+
flowControl: parseFlowControlHeaders(
|
|
2191
|
+
headers.get(FLOW_CONTROL_KEY_HEADER),
|
|
2192
|
+
headers.get(FLOW_CONTROL_VALUE_HEADER)
|
|
2193
|
+
),
|
|
2194
|
+
retries: retriesHeader === null ? void 0 : Number(retriesHeader),
|
|
2195
|
+
retryDelay: headers.get(RETRY_DELAY_HEADER) ?? void 0,
|
|
2196
|
+
hasStepConfig: headers.get(WORKFLOW_STEP_CONFIG_HEADER) === "true"
|
|
2197
|
+
};
|
|
2198
|
+
};
|
|
2199
|
+
var describeStepSettingsMismatch = (stepSettings, effectiveConfig) => {
|
|
2200
|
+
if (stepSettings.flowControl) {
|
|
2201
|
+
const wanted = normalizeFlowControl(stepSettings.flowControl);
|
|
2202
|
+
const applied = effectiveConfig.flowControl;
|
|
2203
|
+
if (!applied || applied.key !== wanted.key || applied.parallelism !== wanted.parallelism || applied.rate !== wanted.rate || applied.period !== wanted.period) {
|
|
2204
|
+
return `flow control: expected ${JSON.stringify(wanted)}, delivery has ${JSON.stringify(applied)}`;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
if (stepSettings.retries !== void 0 && effectiveConfig.retries !== void 0 && stepSettings.retries !== effectiveConfig.retries) {
|
|
2208
|
+
return `retries: expected ${stepSettings.retries}, delivery has ${effectiveConfig.retries}`;
|
|
2209
|
+
}
|
|
2210
|
+
if (stepSettings.retryDelay !== void 0 && stepSettings.retryDelay !== effectiveConfig.retryDelay) {
|
|
2211
|
+
return `retry delay: expected '${stepSettings.retryDelay}', delivery has '${effectiveConfig.retryDelay ?? ""}'`;
|
|
2212
|
+
}
|
|
2213
|
+
return void 0;
|
|
2214
|
+
};
|
|
1978
2215
|
|
|
1979
2216
|
// src/context/auto-executor.ts
|
|
1980
2217
|
var AutoExecutor = class _AutoExecutor {
|
|
@@ -1991,6 +2228,25 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1991
2228
|
stepCount = 0;
|
|
1992
2229
|
planStepCount = 0;
|
|
1993
2230
|
executingStep = false;
|
|
2231
|
+
/**
|
|
2232
|
+
* an executed step whose result hasn't been submitted to QStash yet.
|
|
2233
|
+
*
|
|
2234
|
+
* Set when a step which can produce its result in-process executes. The
|
|
2235
|
+
* submission waits so that the route function can continue and reveal
|
|
2236
|
+
* what comes next: if that is a single step with step-level settings,
|
|
2237
|
+
* the settings ride on this submission and no step config request is
|
|
2238
|
+
* needed, since the delivery of the submission is what executes it.
|
|
2239
|
+
*
|
|
2240
|
+
* Flushed when the next step is reached (see `addStep`) or when the
|
|
2241
|
+
* route function ends (see `flushPendingStep` in `serve`).
|
|
2242
|
+
*/
|
|
2243
|
+
pendingStep;
|
|
2244
|
+
/**
|
|
2245
|
+
* configuration QStash applied to the delivery being handled. A step
|
|
2246
|
+
* with step-level settings is settled by comparing its settings against
|
|
2247
|
+
* this.
|
|
2248
|
+
*/
|
|
2249
|
+
effectiveConfig;
|
|
1994
2250
|
/**
|
|
1995
2251
|
* @param context workflow context
|
|
1996
2252
|
* @param steps list of steps
|
|
@@ -1998,14 +2254,17 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1998
2254
|
* @param dispatchLifecycle lifecycle event dispatcher
|
|
1999
2255
|
* @param telemetry optional telemetry information
|
|
2000
2256
|
* @param invokeCount optional invoke count
|
|
2257
|
+
* @param effectiveConfig configuration QStash applied to the delivery
|
|
2258
|
+
* being handled
|
|
2001
2259
|
*/
|
|
2002
|
-
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount) {
|
|
2260
|
+
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount, effectiveConfig) {
|
|
2003
2261
|
this.context = context;
|
|
2004
2262
|
this.steps = steps;
|
|
2005
2263
|
this.dispatchDebug = dispatchDebug;
|
|
2006
2264
|
this.dispatchLifecycle = dispatchLifecycle;
|
|
2007
2265
|
this.telemetry = telemetry;
|
|
2008
2266
|
this.invokeCount = invokeCount ?? 0;
|
|
2267
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2009
2268
|
this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
|
|
2010
2269
|
}
|
|
2011
2270
|
/**
|
|
@@ -2040,6 +2299,15 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2040
2299
|
lazyStepList.push(stepInfo);
|
|
2041
2300
|
const index = this.indexInCurrentList++;
|
|
2042
2301
|
const requestComplete = this.deferExecution().then(async () => {
|
|
2302
|
+
const submitted = await this.submitPendingStep(
|
|
2303
|
+
lazyStepList.length === 1 ? lazyStepList[0].stepSettings : void 0
|
|
2304
|
+
);
|
|
2305
|
+
if (submitted.isErr()) {
|
|
2306
|
+
throw submitted.error;
|
|
2307
|
+
}
|
|
2308
|
+
if (submitted.value.result === "submitted-step") {
|
|
2309
|
+
throw submitted.value.abort;
|
|
2310
|
+
}
|
|
2043
2311
|
if (!this.promises.has(lazyStepList)) {
|
|
2044
2312
|
const promise2 = this.getExecutionPromise(lazyStepList);
|
|
2045
2313
|
this.promises.set(lazyStepList, promise2);
|
|
@@ -2093,18 +2361,100 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2093
2361
|
}
|
|
2094
2362
|
return parsedOut;
|
|
2095
2363
|
}
|
|
2096
|
-
const
|
|
2097
|
-
|
|
2364
|
+
const mismatch = lazyStep.stepSettings ? describeStepSettingsMismatch(lazyStep.stepSettings, this.effectiveConfig) : void 0;
|
|
2365
|
+
if (mismatch) {
|
|
2366
|
+
if (!this.effectiveConfig.hasStepConfig) {
|
|
2367
|
+
await publishStepConfigRequest({
|
|
2368
|
+
context: this.context,
|
|
2369
|
+
lazyStep,
|
|
2370
|
+
targetStep: this.stepCount,
|
|
2371
|
+
invokeCount: this.invokeCount,
|
|
2372
|
+
telemetry: this.telemetry,
|
|
2373
|
+
dispatchDebug: this.dispatchDebug
|
|
2374
|
+
});
|
|
2375
|
+
throw new WorkflowAbort(lazyStep.stepName);
|
|
2376
|
+
} else {
|
|
2377
|
+
await this.dispatchDebug("onWarning", {
|
|
2378
|
+
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.`
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
const resultStep = await executeStep({
|
|
2098
2383
|
lazyStep,
|
|
2099
2384
|
stepId: this.stepCount,
|
|
2385
|
+
concurrency: NO_CONCURRENCY,
|
|
2386
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2387
|
+
});
|
|
2388
|
+
if (lazyStep.supportsDeferredSubmission) {
|
|
2389
|
+
this.pendingStep = { status: "held", lazyStep, resultStep };
|
|
2390
|
+
return lazyStep.parseOut({
|
|
2391
|
+
...resultStep,
|
|
2392
|
+
out: resultStep.out === void 0 ? void 0 : JSON.stringify(resultStep.out)
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
await submitStepResult({
|
|
2396
|
+
context: this.context,
|
|
2397
|
+
lazyStep,
|
|
2398
|
+
resultStep,
|
|
2100
2399
|
invokeCount: this.invokeCount,
|
|
2101
|
-
concurrency:
|
|
2400
|
+
concurrency: NO_CONCURRENCY,
|
|
2102
2401
|
telemetry: this.telemetry,
|
|
2103
|
-
dispatchDebug: this.dispatchDebug
|
|
2104
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2402
|
+
dispatchDebug: this.dispatchDebug
|
|
2105
2403
|
});
|
|
2106
2404
|
throw new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2107
2405
|
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Submits the result of the step being held, if there is one, and
|
|
2408
|
+
* returns the abort the caller has to throw to end the invocation.
|
|
2409
|
+
*
|
|
2410
|
+
* Returns undefined when no step is being held, which is the caller's
|
|
2411
|
+
* signal that there is nothing to end. Throws instead of returning
|
|
2412
|
+
* when the submission itself fails, so that error reaches the caller
|
|
2413
|
+
* rather than an abort claiming the step was submitted.
|
|
2414
|
+
*
|
|
2415
|
+
* Callers which arrive while a submission is already under way (the
|
|
2416
|
+
* steps of a parallel group, added together) wait for that one and get
|
|
2417
|
+
* the same abort, rather than submitting a second time.
|
|
2418
|
+
*
|
|
2419
|
+
* The next step's settings are attached whenever it has any, without
|
|
2420
|
+
* checking them against the current delivery: the executor only knows
|
|
2421
|
+
* the configuration of the delivery in hand, which inside a
|
|
2422
|
+
* delivery is the *previous* step's, so such a check would be wrong
|
|
2423
|
+
* exactly when two steps with settings follow each other.
|
|
2424
|
+
*
|
|
2425
|
+
* @param nextStepSettings step-level settings of the next step
|
|
2426
|
+
* @returns `submitted-step` with the abort which ends this invocation,
|
|
2427
|
+
* or `no-pending-step` when nothing was held and there is nothing to
|
|
2428
|
+
* end
|
|
2429
|
+
*/
|
|
2430
|
+
async submitPendingStep(nextStepSettings) {
|
|
2431
|
+
if (!this.pendingStep) {
|
|
2432
|
+
return ok({ result: "no-pending-step" });
|
|
2433
|
+
}
|
|
2434
|
+
try {
|
|
2435
|
+
if (this.pendingStep.status === "submitting") {
|
|
2436
|
+
return ok({ result: "submitted-step", abort: await this.pendingStep.submitted });
|
|
2437
|
+
}
|
|
2438
|
+
const { lazyStep, resultStep } = this.pendingStep;
|
|
2439
|
+
const submitted = (async () => {
|
|
2440
|
+
await submitStepResult({
|
|
2441
|
+
context: this.context,
|
|
2442
|
+
lazyStep,
|
|
2443
|
+
resultStep,
|
|
2444
|
+
invokeCount: this.invokeCount,
|
|
2445
|
+
concurrency: NO_CONCURRENCY,
|
|
2446
|
+
telemetry: this.telemetry,
|
|
2447
|
+
dispatchDebug: this.dispatchDebug,
|
|
2448
|
+
nextStepSettings
|
|
2449
|
+
});
|
|
2450
|
+
return new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2451
|
+
})();
|
|
2452
|
+
this.pendingStep = { status: "submitting", submitted };
|
|
2453
|
+
return ok({ result: "submitted-step", abort: await submitted });
|
|
2454
|
+
} catch (error) {
|
|
2455
|
+
return err(error);
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2108
2458
|
/**
|
|
2109
2459
|
* Runs steps in parallel.
|
|
2110
2460
|
*
|
|
@@ -2153,15 +2503,20 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2153
2503
|
validateStep(parallelSteps[stepIndex], planStep);
|
|
2154
2504
|
try {
|
|
2155
2505
|
const parallelStep = parallelSteps[stepIndex];
|
|
2156
|
-
const resultStep = await
|
|
2157
|
-
context: this.context,
|
|
2506
|
+
const resultStep = await executeStep({
|
|
2158
2507
|
lazyStep: parallelStep,
|
|
2159
2508
|
stepId: planStep.targetStep,
|
|
2509
|
+
concurrency: parallelSteps.length,
|
|
2510
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2511
|
+
});
|
|
2512
|
+
await submitStepResult({
|
|
2513
|
+
context: this.context,
|
|
2514
|
+
lazyStep: parallelStep,
|
|
2515
|
+
resultStep,
|
|
2160
2516
|
invokeCount: this.invokeCount,
|
|
2161
2517
|
concurrency: parallelSteps.length,
|
|
2162
2518
|
telemetry: this.telemetry,
|
|
2163
|
-
dispatchDebug: this.dispatchDebug
|
|
2164
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2519
|
+
dispatchDebug: this.dispatchDebug
|
|
2165
2520
|
});
|
|
2166
2521
|
throw new WorkflowAbort(parallelStep.stepName, resultStep);
|
|
2167
2522
|
} catch (error) {
|
|
@@ -2710,6 +3065,46 @@ var WorkflowContext = class {
|
|
|
2710
3065
|
* `1` on the first retry, `2` on the second, and so on.
|
|
2711
3066
|
*/
|
|
2712
3067
|
retried;
|
|
3068
|
+
/**
|
|
3069
|
+
* Configuration QStash applied to the request being handled.
|
|
3070
|
+
*
|
|
3071
|
+
* This is the configuration of *this delivery*, not of the run: it is
|
|
3072
|
+
* the configuration the run was triggered with, except inside the
|
|
3073
|
+
* delivery which executes a step that has step-level settings, where
|
|
3074
|
+
* it is that step's settings. The fields below expose it; the executor
|
|
3075
|
+
* gets it directly rather than reaching through the context.
|
|
3076
|
+
*/
|
|
3077
|
+
effectiveConfig;
|
|
3078
|
+
/**
|
|
3079
|
+
* Flow control QStash applied to the request being handled, if any.
|
|
3080
|
+
*
|
|
3081
|
+
* This is the flow control of the request in hand, which is the one the
|
|
3082
|
+
* run was triggered with except inside a step carrying its own.
|
|
3083
|
+
*/
|
|
3084
|
+
get flowControl() {
|
|
3085
|
+
return this.effectiveConfig.flowControl;
|
|
3086
|
+
}
|
|
3087
|
+
/**
|
|
3088
|
+
* Retry limit QStash applied to the request being handled, if it
|
|
3089
|
+
* reported one.
|
|
3090
|
+
*
|
|
3091
|
+
* Not to be confused with {@link retried}, which is how many retries
|
|
3092
|
+
* have already happened.
|
|
3093
|
+
*
|
|
3094
|
+
* @see {@link flowControl} for what "the request being handled" means
|
|
3095
|
+
*/
|
|
3096
|
+
get retries() {
|
|
3097
|
+
return this.effectiveConfig.retries;
|
|
3098
|
+
}
|
|
3099
|
+
/**
|
|
3100
|
+
* Retry delay expression QStash applied to the request being handled,
|
|
3101
|
+
* if any.
|
|
3102
|
+
*
|
|
3103
|
+
* @see {@link flowControl} for what "the request being handled" means
|
|
3104
|
+
*/
|
|
3105
|
+
get retryDelay() {
|
|
3106
|
+
return this.effectiveConfig.retryDelay;
|
|
3107
|
+
}
|
|
2713
3108
|
constructor({
|
|
2714
3109
|
qstashClient,
|
|
2715
3110
|
workflowRunId,
|
|
@@ -2723,7 +3118,8 @@ var WorkflowContext = class {
|
|
|
2723
3118
|
invokeCount,
|
|
2724
3119
|
label,
|
|
2725
3120
|
retried,
|
|
2726
|
-
middlewareManager
|
|
3121
|
+
middlewareManager,
|
|
3122
|
+
effectiveConfig
|
|
2727
3123
|
}) {
|
|
2728
3124
|
this.qstashClient = qstashClient;
|
|
2729
3125
|
this.workflowRunId = workflowRunId;
|
|
@@ -2735,6 +3131,7 @@ var WorkflowContext = class {
|
|
|
2735
3131
|
this.env = env ?? {};
|
|
2736
3132
|
this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
|
|
2737
3133
|
this.retried = retried ?? 0;
|
|
3134
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2738
3135
|
const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
|
|
2739
3136
|
middlewareManagerInstance.assignContext(this);
|
|
2740
3137
|
this.executor = new AutoExecutor(
|
|
@@ -2743,7 +3140,8 @@ var WorkflowContext = class {
|
|
|
2743
3140
|
middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
|
|
2744
3141
|
middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
|
|
2745
3142
|
telemetry,
|
|
2746
|
-
invokeCount
|
|
3143
|
+
invokeCount,
|
|
3144
|
+
this.effectiveConfig
|
|
2747
3145
|
);
|
|
2748
3146
|
}
|
|
2749
3147
|
/**
|
|
@@ -2769,13 +3167,35 @@ var WorkflowContext = class {
|
|
|
2769
3167
|
* ])
|
|
2770
3168
|
* ```
|
|
2771
3169
|
*
|
|
3170
|
+
* Step-level settings can be passed as a third argument, overriding
|
|
3171
|
+
* the settings the workflow run was triggered with for this step only:
|
|
3172
|
+
*
|
|
3173
|
+
* ```typescript
|
|
3174
|
+
* const result = await context.run(
|
|
3175
|
+
* "step 1",
|
|
3176
|
+
* () => {
|
|
3177
|
+
* return "result"
|
|
3178
|
+
* },
|
|
3179
|
+
* {
|
|
3180
|
+
* flowControl: { key: "custom-key", parallelism: 3 },
|
|
3181
|
+
* retries: 5,
|
|
3182
|
+
* }
|
|
3183
|
+
* )
|
|
3184
|
+
* ```
|
|
3185
|
+
*
|
|
2772
3186
|
* @param stepName name of the step
|
|
2773
3187
|
* @param stepFunction step function to be executed
|
|
3188
|
+
* @param stepSettings step-level settings for this step
|
|
2774
3189
|
* @returns result of the step function
|
|
2775
3190
|
*/
|
|
2776
|
-
async run(stepName, stepFunction) {
|
|
3191
|
+
async run(stepName, stepFunction, stepSettings) {
|
|
3192
|
+
if (stepSettings) {
|
|
3193
|
+
validateFlowControl(stepSettings.flowControl);
|
|
3194
|
+
}
|
|
2777
3195
|
const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
|
|
2778
|
-
return await this.addStep(
|
|
3196
|
+
return await this.addStep(
|
|
3197
|
+
new LazyFunctionStep(this, stepName, wrappedStepFunction, stepSettings)
|
|
3198
|
+
);
|
|
2779
3199
|
}
|
|
2780
3200
|
/**
|
|
2781
3201
|
* Stops the execution for the duration provided.
|
|
@@ -2998,7 +3418,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
2998
3418
|
*
|
|
2999
3419
|
* @param routeFunction
|
|
3000
3420
|
*/
|
|
3001
|
-
static async tryAuthentication(routeFunction, context) {
|
|
3421
|
+
static async tryAuthentication(routeFunction, context, effectiveConfig) {
|
|
3002
3422
|
const disabledContext = new _DisabledWorkflowContext({
|
|
3003
3423
|
qstashClient: new import_qstash10.Client({
|
|
3004
3424
|
baseUrl: "disabled-client",
|
|
@@ -3013,7 +3433,10 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3013
3433
|
initialPayload: context.requestPayload,
|
|
3014
3434
|
env: context.env,
|
|
3015
3435
|
label: context.labels,
|
|
3016
|
-
retried: context.retried
|
|
3436
|
+
retried: context.retried,
|
|
3437
|
+
// the route function runs for real here until it reaches a step, so
|
|
3438
|
+
// it observes the same configuration as it will on the real context
|
|
3439
|
+
effectiveConfig
|
|
3017
3440
|
});
|
|
3018
3441
|
try {
|
|
3019
3442
|
await routeFunction(disabledContext);
|
|
@@ -3230,6 +3653,7 @@ var handleFailure = async ({
|
|
|
3230
3653
|
}
|
|
3231
3654
|
const userHeaders = recreateUserHeaders(request.headers);
|
|
3232
3655
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3656
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3233
3657
|
const workflowContext = new WorkflowContext({
|
|
3234
3658
|
qstashClient,
|
|
3235
3659
|
workflowRunId,
|
|
@@ -3243,11 +3667,13 @@ var handleFailure = async ({
|
|
|
3243
3667
|
label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
|
|
3244
3668
|
retried,
|
|
3245
3669
|
workflowRunCreatedAt: workflowCreatedAt,
|
|
3246
|
-
middlewareManager: void 0
|
|
3670
|
+
middlewareManager: void 0,
|
|
3671
|
+
effectiveConfig
|
|
3247
3672
|
});
|
|
3248
3673
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3249
3674
|
routeFunction,
|
|
3250
|
-
workflowContext
|
|
3675
|
+
workflowContext,
|
|
3676
|
+
effectiveConfig
|
|
3251
3677
|
);
|
|
3252
3678
|
if (authCheck.isErr()) {
|
|
3253
3679
|
await dispatchDebug?.("onError", {
|
|
@@ -3339,7 +3765,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
|
|
|
3339
3765
|
const client = new import_qstash11.Client({
|
|
3340
3766
|
...clientOptions,
|
|
3341
3767
|
baseUrl: clientEnv.QSTASH_URL,
|
|
3342
|
-
token: clientEnv.QSTASH_TOKEN
|
|
3768
|
+
token: clientEnv.QSTASH_TOKEN,
|
|
3769
|
+
// Mirror the receiver's dev-mode decision so a single QSTASH_DEV=true points
|
|
3770
|
+
// the client at the local dev server too. Without this the QStash Client
|
|
3771
|
+
// re-derives dev mode from its own process.env, which is blind to the
|
|
3772
|
+
// Cloudflare worker `env` binding that actually carries QSTASH_DEV there.
|
|
3773
|
+
// An explicit devMode in the user's client config still wins.
|
|
3774
|
+
devMode: clientOptions?.devMode ?? isQStashDevModeEnabled(environment)
|
|
3343
3775
|
});
|
|
3344
3776
|
const receiver = getReceiver(environment, receiverConfig, region);
|
|
3345
3777
|
return { client, receiver };
|
|
@@ -3389,7 +3821,9 @@ var getQStashHandlers = ({
|
|
|
3389
3821
|
client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
|
|
3390
3822
|
...qstashClientOption,
|
|
3391
3823
|
baseUrl: environment.QSTASH_URL,
|
|
3392
|
-
token: environment.QSTASH_TOKEN
|
|
3824
|
+
token: environment.QSTASH_TOKEN,
|
|
3825
|
+
// Mirror the receiver's dev-mode decision (see createRegionalHandler).
|
|
3826
|
+
devMode: qstashClientOption?.devMode ?? isQStashDevModeEnabled(environment)
|
|
3393
3827
|
}),
|
|
3394
3828
|
receiver: getReceiver(environment, receiverConfig)
|
|
3395
3829
|
}
|
|
@@ -3729,12 +4163,14 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3729
4163
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3730
4164
|
const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
|
|
3731
4165
|
const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
4166
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3732
4167
|
const workflowContext = new WorkflowContext({
|
|
3733
4168
|
qstashClient: regionalClient,
|
|
3734
4169
|
workflowRunId,
|
|
3735
4170
|
initialPayload: isThirdPartyCallResult(request) ? JSON.parse(rawInitialPayload) : initialPayloadParser(rawInitialPayload),
|
|
3736
4171
|
headers: recreateUserHeaders(request.headers),
|
|
3737
4172
|
steps,
|
|
4173
|
+
effectiveConfig,
|
|
3738
4174
|
url: workflowUrl,
|
|
3739
4175
|
env,
|
|
3740
4176
|
telemetry,
|
|
@@ -3746,7 +4182,8 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3746
4182
|
});
|
|
3747
4183
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3748
4184
|
routeFunction,
|
|
3749
|
-
workflowContext
|
|
4185
|
+
workflowContext,
|
|
4186
|
+
effectiveConfig
|
|
3750
4187
|
);
|
|
3751
4188
|
if (authCheck.isErr()) {
|
|
3752
4189
|
throw authCheck.error;
|
|
@@ -3783,7 +4220,23 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3783
4220
|
if (steps.length === 1) {
|
|
3784
4221
|
await middlewareManager.dispatchLifecycle("runStarted", {});
|
|
3785
4222
|
}
|
|
3786
|
-
|
|
4223
|
+
let outcome;
|
|
4224
|
+
try {
|
|
4225
|
+
outcome = { ran: true, result: await routeFunction(workflowContext) };
|
|
4226
|
+
} catch (error) {
|
|
4227
|
+
outcome = { ran: false, error };
|
|
4228
|
+
}
|
|
4229
|
+
const submitted = await flushPendingStep(workflowContext);
|
|
4230
|
+
if (submitted.isErr()) {
|
|
4231
|
+
throw submitted.error;
|
|
4232
|
+
}
|
|
4233
|
+
if (submitted.value.result === "submitted-step") {
|
|
4234
|
+
throw submitted.value.abort;
|
|
4235
|
+
}
|
|
4236
|
+
if (!outcome.ran) {
|
|
4237
|
+
throw outcome.error;
|
|
4238
|
+
}
|
|
4239
|
+
return outcome.result;
|
|
3787
4240
|
},
|
|
3788
4241
|
onCleanup: async (result2) => {
|
|
3789
4242
|
await middlewareManager.dispatchLifecycle("runCompleted", {
|
|
@@ -3864,7 +4317,9 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3864
4317
|
};
|
|
3865
4318
|
|
|
3866
4319
|
// platforms/solidjs.ts
|
|
4320
|
+
var import_qstash13 = require("@upstash/qstash");
|
|
3867
4321
|
var serve = (routeFunction, options) => {
|
|
4322
|
+
void (0, import_qstash13.startDevServer)();
|
|
3868
4323
|
const telemetry = {
|
|
3869
4324
|
sdk: SDK_TELEMETRY,
|
|
3870
4325
|
framework: "solidjs",
|