@upstash/workflow 1.3.3 → 1.4.0-rc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/astro.d.mts +3 -2
- package/astro.d.ts +3 -2
- package/astro.js +587 -139
- package/astro.mjs +3 -1
- package/{chunk-WEQGCISK.mjs → chunk-UH6O5IJY.mjs} +749 -301
- package/cloudflare.d.mts +3 -2
- package/cloudflare.d.ts +3 -2
- package/cloudflare.js +587 -139
- package/cloudflare.mjs +3 -1
- package/express.d.mts +3 -2
- package/express.d.ts +3 -2
- package/express.js +588 -139
- package/express.mjs +4 -1
- package/h3.d.mts +3 -2
- package/h3.d.ts +3 -2
- package/h3.js +587 -139
- package/h3.mjs +3 -1
- package/hono.d.mts +3 -2
- package/hono.d.ts +3 -2
- package/hono.js +587 -139
- package/hono.mjs +3 -1
- package/index.d.mts +25 -6
- package/index.d.ts +25 -6
- package/index.js +610 -141
- package/index.mjs +26 -3
- package/nextjs.d.mts +3 -2
- package/nextjs.d.ts +3 -2
- package/nextjs.js +588 -139
- package/nextjs.mjs +4 -1
- package/package.json +1 -1
- package/react-router.d.mts +3 -2
- package/react-router.d.ts +3 -2
- package/react-router.js +587 -139
- package/react-router.mjs +3 -1
- package/{serve-many-iJF1IUXk.d.ts → serve-many-BCtZg31b.d.ts} +1 -1
- package/{serve-many-CG3BFvO3.d.mts → serve-many-CbIV7145.d.mts} +1 -1
- package/solidjs.d.mts +2 -1
- package/solidjs.d.ts +2 -1
- package/solidjs.js +587 -139
- package/solidjs.mjs +3 -1
- package/svelte.d.mts +3 -2
- package/svelte.d.ts +3 -2
- package/svelte.js +587 -139
- package/svelte.mjs +3 -1
- package/tanstack.d.mts +3 -2
- package/tanstack.d.ts +3 -2
- package/tanstack.js +587 -139
- package/tanstack.mjs +3 -1
- package/{types-CekOpKvz.d.ts → types-ffLPVG5_.d.mts} +248 -8
- package/{types-CekOpKvz.d.mts → types-ffLPVG5_.d.ts} +248 -8
package/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
|
|
|
@@ -212,6 +215,14 @@ var WORKFLOW_CREATED_AT_HEADER = "Upstash-Workflow-CreatedAt";
|
|
|
212
215
|
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
|
|
213
216
|
var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
|
|
214
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";
|
|
215
226
|
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
|
|
216
227
|
var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
|
|
217
228
|
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
|
|
@@ -229,102 +240,6 @@ var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
|
|
|
229
240
|
var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
|
|
230
241
|
var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
|
|
231
242
|
|
|
232
|
-
// src/context/auto-executor.ts
|
|
233
|
-
var import_qstash5 = require("@upstash/qstash");
|
|
234
|
-
|
|
235
|
-
// src/qstash/headers.ts
|
|
236
|
-
var import_qstash4 = require("@upstash/qstash");
|
|
237
|
-
|
|
238
|
-
// src/utils.ts
|
|
239
|
-
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
240
|
-
var NANOID_LENGTH = 21;
|
|
241
|
-
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
242
|
-
function validateLabel(label) {
|
|
243
|
-
if (label === void 0) return;
|
|
244
|
-
const labels = Array.isArray(label) ? label : [label];
|
|
245
|
-
if (labels.length === 0) {
|
|
246
|
-
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
247
|
-
}
|
|
248
|
-
for (const value of labels) {
|
|
249
|
-
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
250
|
-
throw new WorkflowNonRetryableError(
|
|
251
|
-
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
function serializeLabel(label) {
|
|
257
|
-
return Array.isArray(label) ? label.join(",") : label;
|
|
258
|
-
}
|
|
259
|
-
function validateFlowControl(flowControl) {
|
|
260
|
-
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
261
|
-
throw new WorkflowNonRetryableError(
|
|
262
|
-
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function getRandomInt() {
|
|
267
|
-
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
268
|
-
}
|
|
269
|
-
function nanoid(length = NANOID_LENGTH) {
|
|
270
|
-
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
271
|
-
}
|
|
272
|
-
function getWorkflowRunId(id) {
|
|
273
|
-
return `wfr_${id ?? nanoid()}`;
|
|
274
|
-
}
|
|
275
|
-
function decodeBase64(base64) {
|
|
276
|
-
const binString = atob(base64);
|
|
277
|
-
try {
|
|
278
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
279
|
-
return new TextDecoder().decode(intArray);
|
|
280
|
-
} catch (error) {
|
|
281
|
-
console.warn(
|
|
282
|
-
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
283
|
-
);
|
|
284
|
-
return binString;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
function getUserIdFromToken(qstashClient) {
|
|
288
|
-
try {
|
|
289
|
-
const token = qstashClient.token;
|
|
290
|
-
const decodedToken = decodeBase64(token);
|
|
291
|
-
const tokenPayload = JSON.parse(decodedToken);
|
|
292
|
-
const userId = tokenPayload.UserID;
|
|
293
|
-
if (!userId) {
|
|
294
|
-
throw new WorkflowError("QStash token payload does not contain userId");
|
|
295
|
-
}
|
|
296
|
-
return userId;
|
|
297
|
-
} catch (error) {
|
|
298
|
-
throw new WorkflowError(
|
|
299
|
-
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
function getQStashUrl(qstashClient) {
|
|
304
|
-
try {
|
|
305
|
-
const requester = qstashClient.http;
|
|
306
|
-
const baseUrl = requester.baseUrl;
|
|
307
|
-
if (!baseUrl) {
|
|
308
|
-
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
309
|
-
}
|
|
310
|
-
return baseUrl;
|
|
311
|
-
} catch (error) {
|
|
312
|
-
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
function getEventId() {
|
|
316
|
-
return `evt_${nanoid(15)}`;
|
|
317
|
-
}
|
|
318
|
-
function stringifyBody(body) {
|
|
319
|
-
if (body === void 0) {
|
|
320
|
-
return void 0;
|
|
321
|
-
}
|
|
322
|
-
if (typeof body === "string") {
|
|
323
|
-
return body;
|
|
324
|
-
}
|
|
325
|
-
return JSON.stringify(body);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
243
|
// node_modules/neverthrow/dist/index.es.js
|
|
329
244
|
var defaultErrorConfig = {
|
|
330
245
|
withStackTrace: false
|
|
@@ -740,6 +655,102 @@ var Err = class {
|
|
|
740
655
|
};
|
|
741
656
|
var fromThrowable = Result.fromThrowable;
|
|
742
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
|
+
|
|
743
754
|
// src/types.ts
|
|
744
755
|
var StepTypes = [
|
|
745
756
|
"Initial",
|
|
@@ -845,6 +856,10 @@ var triggerFirstInvocation = async (params) => {
|
|
|
845
856
|
return err(error_);
|
|
846
857
|
}
|
|
847
858
|
};
|
|
859
|
+
var flushPendingStep = async (workflowContext) => {
|
|
860
|
+
const { executor } = workflowContext;
|
|
861
|
+
return await executor.submitPendingStep();
|
|
862
|
+
};
|
|
848
863
|
var triggerRouteFunction = async ({
|
|
849
864
|
onCleanup,
|
|
850
865
|
onStep,
|
|
@@ -1052,8 +1067,29 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
|
|
|
1052
1067
|
var BaseLazyStep = class _BaseLazyStep {
|
|
1053
1068
|
stepName;
|
|
1054
1069
|
context;
|
|
1055
|
-
|
|
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) {
|
|
1056
1091
|
this.context = context;
|
|
1092
|
+
this.stepSettings = stepSettings;
|
|
1057
1093
|
if (!stepName) {
|
|
1058
1094
|
throw new WorkflowError(
|
|
1059
1095
|
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
|
|
@@ -1117,7 +1153,13 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1117
1153
|
step.out = JSON.stringify(step.out);
|
|
1118
1154
|
return JSON.stringify(step);
|
|
1119
1155
|
}
|
|
1120
|
-
getHeaders({
|
|
1156
|
+
getHeaders({
|
|
1157
|
+
context,
|
|
1158
|
+
telemetry,
|
|
1159
|
+
invokeCount,
|
|
1160
|
+
step,
|
|
1161
|
+
stepSettings
|
|
1162
|
+
}) {
|
|
1121
1163
|
return getHeaders({
|
|
1122
1164
|
initHeaderValue: "false",
|
|
1123
1165
|
workflowConfig: {
|
|
@@ -1130,7 +1172,8 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1130
1172
|
stepInfo: {
|
|
1131
1173
|
step,
|
|
1132
1174
|
lazyStep: this
|
|
1133
|
-
}
|
|
1175
|
+
},
|
|
1176
|
+
stepSettings
|
|
1134
1177
|
});
|
|
1135
1178
|
}
|
|
1136
1179
|
async submitStep({ context, body, headers }) {
|
|
@@ -1148,8 +1191,9 @@ var LazyFunctionStep = class extends BaseLazyStep {
|
|
|
1148
1191
|
stepFunction;
|
|
1149
1192
|
stepType = "Run";
|
|
1150
1193
|
allowUndefinedOut = true;
|
|
1151
|
-
|
|
1152
|
-
|
|
1194
|
+
supportsDeferredSubmission = true;
|
|
1195
|
+
constructor(context, stepName, stepFunction, stepSettings) {
|
|
1196
|
+
super(context, stepName, stepSettings);
|
|
1153
1197
|
this.stepFunction = stepFunction;
|
|
1154
1198
|
}
|
|
1155
1199
|
getPlanStep(concurrent, targetStep) {
|
|
@@ -1179,6 +1223,7 @@ var LazySleepStep = class extends BaseLazyStep {
|
|
|
1179
1223
|
sleep;
|
|
1180
1224
|
stepType = "SleepFor";
|
|
1181
1225
|
allowUndefinedOut = true;
|
|
1226
|
+
supportsDeferredSubmission = true;
|
|
1182
1227
|
constructor(context, stepName, sleep) {
|
|
1183
1228
|
super(context, stepName);
|
|
1184
1229
|
this.sleep = sleep;
|
|
@@ -1218,6 +1263,7 @@ var LazySleepUntilStep = class extends BaseLazyStep {
|
|
|
1218
1263
|
sleepUntil;
|
|
1219
1264
|
stepType = "SleepUntil";
|
|
1220
1265
|
allowUndefinedOut = true;
|
|
1266
|
+
supportsDeferredSubmission = true;
|
|
1221
1267
|
constructor(context, stepName, sleepUntil) {
|
|
1222
1268
|
super(context, stepName);
|
|
1223
1269
|
this.sleepUntil = sleepUntil;
|
|
@@ -1710,6 +1756,7 @@ var WorkflowHeaders = class {
|
|
|
1710
1756
|
invokeCount;
|
|
1711
1757
|
initHeaderValue;
|
|
1712
1758
|
stepInfo;
|
|
1759
|
+
stepSettings;
|
|
1713
1760
|
headers;
|
|
1714
1761
|
/**
|
|
1715
1762
|
* @param params workflow header parameters
|
|
@@ -1719,17 +1766,20 @@ var WorkflowHeaders = class {
|
|
|
1719
1766
|
workflowConfig,
|
|
1720
1767
|
invokeCount,
|
|
1721
1768
|
initHeaderValue,
|
|
1722
|
-
stepInfo
|
|
1769
|
+
stepInfo,
|
|
1770
|
+
stepSettings
|
|
1723
1771
|
}) {
|
|
1724
1772
|
this.userHeaders = userHeaders;
|
|
1725
1773
|
this.workflowConfig = workflowConfig;
|
|
1726
1774
|
this.invokeCount = invokeCount;
|
|
1727
1775
|
this.initHeaderValue = initHeaderValue;
|
|
1728
1776
|
this.stepInfo = stepInfo;
|
|
1777
|
+
this.stepSettings = stepSettings;
|
|
1729
1778
|
this.headers = {
|
|
1730
1779
|
rawHeaders: {},
|
|
1731
1780
|
workflowHeaders: {},
|
|
1732
|
-
failureHeaders: {}
|
|
1781
|
+
failureHeaders: {},
|
|
1782
|
+
stepSettingsHeaders: {}
|
|
1733
1783
|
};
|
|
1734
1784
|
}
|
|
1735
1785
|
getHeaders() {
|
|
@@ -1740,6 +1790,7 @@ var WorkflowHeaders = class {
|
|
|
1740
1790
|
this.addUserHeaders();
|
|
1741
1791
|
this.addInvokeCount();
|
|
1742
1792
|
this.addFailureUrl();
|
|
1793
|
+
this.addStepSettings();
|
|
1743
1794
|
const contentType = this.addContentType();
|
|
1744
1795
|
return this.prefixHeaders(contentType);
|
|
1745
1796
|
}
|
|
@@ -1749,7 +1800,7 @@ var WorkflowHeaders = class {
|
|
|
1749
1800
|
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
|
|
1750
1801
|
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
|
|
1751
1802
|
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
|
|
1752
|
-
[WORKFLOW_FEATURE_HEADER]:
|
|
1803
|
+
[WORKFLOW_FEATURE_HEADER]: WORKFLOW_FEATURE_SET,
|
|
1753
1804
|
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
1754
1805
|
...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
|
|
1755
1806
|
};
|
|
@@ -1827,7 +1878,7 @@ var WorkflowHeaders = class {
|
|
|
1827
1878
|
this.headers.failureHeaders["Workflow-Init"] = "false";
|
|
1828
1879
|
this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
|
|
1829
1880
|
this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
|
|
1830
|
-
this.headers.failureHeaders["Feature-Set"] =
|
|
1881
|
+
this.headers.failureHeaders["Feature-Set"] = WORKFLOW_FEATURE_SET;
|
|
1831
1882
|
if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
|
|
1832
1883
|
this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
|
|
1833
1884
|
}
|
|
@@ -1835,6 +1886,42 @@ var WorkflowHeaders = class {
|
|
|
1835
1886
|
this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
|
|
1836
1887
|
}
|
|
1837
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
|
+
}
|
|
1838
1925
|
addContentType() {
|
|
1839
1926
|
if (this.workflowConfig.useJSONContent) {
|
|
1840
1927
|
this.headers.rawHeaders["content-type"] = "application/json";
|
|
@@ -1848,14 +1935,17 @@ var WorkflowHeaders = class {
|
|
|
1848
1935
|
return contentType;
|
|
1849
1936
|
}
|
|
1850
1937
|
prefixHeaders(contentType) {
|
|
1851
|
-
const { rawHeaders, workflowHeaders, failureHeaders } = this.headers;
|
|
1938
|
+
const { rawHeaders, workflowHeaders, failureHeaders, stepSettingsHeaders } = this.headers;
|
|
1852
1939
|
const isCall = this.stepInfo?.lazyStep.stepType === "Call";
|
|
1853
1940
|
return {
|
|
1854
1941
|
headers: {
|
|
1855
1942
|
...rawHeaders,
|
|
1856
1943
|
...addPrefixToHeaders(workflowHeaders, isCall ? "Upstash-Callback-" : "Upstash-"),
|
|
1857
1944
|
...addPrefixToHeaders(failureHeaders, "Upstash-Failure-Callback-"),
|
|
1858
|
-
...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
|
|
1859
1949
|
},
|
|
1860
1950
|
contentType
|
|
1861
1951
|
};
|
|
@@ -1906,7 +1996,7 @@ var submitParallelSteps = async ({
|
|
|
1906
1996
|
info: `Submitting ${planSteps.length} parallel steps.`
|
|
1907
1997
|
});
|
|
1908
1998
|
const result = await context.qstashClient.batch(
|
|
1909
|
-
planSteps.map((planStep) => {
|
|
1999
|
+
planSteps.map((planStep, index) => {
|
|
1910
2000
|
const { headers } = getHeaders({
|
|
1911
2001
|
initHeaderValue: "false",
|
|
1912
2002
|
workflowConfig: {
|
|
@@ -1914,7 +2004,8 @@ var submitParallelSteps = async ({
|
|
|
1914
2004
|
workflowUrl: context.url,
|
|
1915
2005
|
telemetry
|
|
1916
2006
|
},
|
|
1917
|
-
invokeCount
|
|
2007
|
+
invokeCount,
|
|
2008
|
+
stepSettings: steps[index].stepSettings
|
|
1918
2009
|
});
|
|
1919
2010
|
return {
|
|
1920
2011
|
headers,
|
|
@@ -1933,31 +2024,38 @@ var submitParallelSteps = async ({
|
|
|
1933
2024
|
}
|
|
1934
2025
|
throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
|
|
1935
2026
|
};
|
|
1936
|
-
var
|
|
1937
|
-
context,
|
|
2027
|
+
var executeStep = async ({
|
|
1938
2028
|
lazyStep,
|
|
1939
2029
|
stepId,
|
|
1940
|
-
invokeCount,
|
|
1941
2030
|
concurrency,
|
|
1942
|
-
telemetry,
|
|
1943
|
-
dispatchDebug,
|
|
1944
2031
|
dispatchLifecycle
|
|
1945
2032
|
}) => {
|
|
1946
2033
|
await dispatchLifecycle("beforeExecution", {
|
|
1947
2034
|
stepName: lazyStep.stepName
|
|
1948
2035
|
});
|
|
1949
|
-
let resultStep;
|
|
1950
2036
|
try {
|
|
1951
|
-
|
|
2037
|
+
return await lazyStep.getResultStep(concurrency, stepId);
|
|
1952
2038
|
} catch (error) {
|
|
1953
2039
|
attachStepNameToError(error, lazyStep.stepName);
|
|
1954
2040
|
throw error;
|
|
1955
2041
|
}
|
|
2042
|
+
};
|
|
2043
|
+
var submitStepResult = async ({
|
|
2044
|
+
context,
|
|
2045
|
+
lazyStep,
|
|
2046
|
+
resultStep,
|
|
2047
|
+
invokeCount,
|
|
2048
|
+
concurrency,
|
|
2049
|
+
telemetry,
|
|
2050
|
+
dispatchDebug,
|
|
2051
|
+
nextStepSettings
|
|
2052
|
+
}) => {
|
|
1956
2053
|
const { headers } = lazyStep.getHeaders({
|
|
1957
2054
|
context,
|
|
1958
2055
|
step: resultStep,
|
|
1959
2056
|
invokeCount,
|
|
1960
|
-
telemetry
|
|
2057
|
+
telemetry,
|
|
2058
|
+
stepSettings: nextStepSettings
|
|
1961
2059
|
});
|
|
1962
2060
|
const body = lazyStep.getBody({
|
|
1963
2061
|
context,
|
|
@@ -1982,6 +2080,138 @@ var submitSingleStep = async ({
|
|
|
1982
2080
|
}
|
|
1983
2081
|
return resultStep;
|
|
1984
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
|
+
};
|
|
1985
2215
|
|
|
1986
2216
|
// src/context/auto-executor.ts
|
|
1987
2217
|
var AutoExecutor = class _AutoExecutor {
|
|
@@ -1998,6 +2228,25 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1998
2228
|
stepCount = 0;
|
|
1999
2229
|
planStepCount = 0;
|
|
2000
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;
|
|
2001
2250
|
/**
|
|
2002
2251
|
* @param context workflow context
|
|
2003
2252
|
* @param steps list of steps
|
|
@@ -2005,14 +2254,17 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2005
2254
|
* @param dispatchLifecycle lifecycle event dispatcher
|
|
2006
2255
|
* @param telemetry optional telemetry information
|
|
2007
2256
|
* @param invokeCount optional invoke count
|
|
2257
|
+
* @param effectiveConfig configuration QStash applied to the delivery
|
|
2258
|
+
* being handled
|
|
2008
2259
|
*/
|
|
2009
|
-
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount) {
|
|
2260
|
+
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount, effectiveConfig) {
|
|
2010
2261
|
this.context = context;
|
|
2011
2262
|
this.steps = steps;
|
|
2012
2263
|
this.dispatchDebug = dispatchDebug;
|
|
2013
2264
|
this.dispatchLifecycle = dispatchLifecycle;
|
|
2014
2265
|
this.telemetry = telemetry;
|
|
2015
2266
|
this.invokeCount = invokeCount ?? 0;
|
|
2267
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2016
2268
|
this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
|
|
2017
2269
|
}
|
|
2018
2270
|
/**
|
|
@@ -2047,6 +2299,15 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2047
2299
|
lazyStepList.push(stepInfo);
|
|
2048
2300
|
const index = this.indexInCurrentList++;
|
|
2049
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
|
+
}
|
|
2050
2311
|
if (!this.promises.has(lazyStepList)) {
|
|
2051
2312
|
const promise2 = this.getExecutionPromise(lazyStepList);
|
|
2052
2313
|
this.promises.set(lazyStepList, promise2);
|
|
@@ -2100,18 +2361,100 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2100
2361
|
}
|
|
2101
2362
|
return parsedOut;
|
|
2102
2363
|
}
|
|
2103
|
-
const
|
|
2104
|
-
|
|
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({
|
|
2105
2383
|
lazyStep,
|
|
2106
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,
|
|
2107
2399
|
invokeCount: this.invokeCount,
|
|
2108
|
-
concurrency:
|
|
2400
|
+
concurrency: NO_CONCURRENCY,
|
|
2109
2401
|
telemetry: this.telemetry,
|
|
2110
|
-
dispatchDebug: this.dispatchDebug
|
|
2111
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2402
|
+
dispatchDebug: this.dispatchDebug
|
|
2112
2403
|
});
|
|
2113
2404
|
throw new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2114
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
|
+
}
|
|
2115
2458
|
/**
|
|
2116
2459
|
* Runs steps in parallel.
|
|
2117
2460
|
*
|
|
@@ -2160,15 +2503,20 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2160
2503
|
validateStep(parallelSteps[stepIndex], planStep);
|
|
2161
2504
|
try {
|
|
2162
2505
|
const parallelStep = parallelSteps[stepIndex];
|
|
2163
|
-
const resultStep = await
|
|
2164
|
-
context: this.context,
|
|
2506
|
+
const resultStep = await executeStep({
|
|
2165
2507
|
lazyStep: parallelStep,
|
|
2166
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,
|
|
2167
2516
|
invokeCount: this.invokeCount,
|
|
2168
2517
|
concurrency: parallelSteps.length,
|
|
2169
2518
|
telemetry: this.telemetry,
|
|
2170
|
-
dispatchDebug: this.dispatchDebug
|
|
2171
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2519
|
+
dispatchDebug: this.dispatchDebug
|
|
2172
2520
|
});
|
|
2173
2521
|
throw new WorkflowAbort(parallelStep.stepName, resultStep);
|
|
2174
2522
|
} catch (error) {
|
|
@@ -2717,6 +3065,46 @@ var WorkflowContext = class {
|
|
|
2717
3065
|
* `1` on the first retry, `2` on the second, and so on.
|
|
2718
3066
|
*/
|
|
2719
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
|
+
}
|
|
2720
3108
|
constructor({
|
|
2721
3109
|
qstashClient,
|
|
2722
3110
|
workflowRunId,
|
|
@@ -2730,7 +3118,8 @@ var WorkflowContext = class {
|
|
|
2730
3118
|
invokeCount,
|
|
2731
3119
|
label,
|
|
2732
3120
|
retried,
|
|
2733
|
-
middlewareManager
|
|
3121
|
+
middlewareManager,
|
|
3122
|
+
effectiveConfig
|
|
2734
3123
|
}) {
|
|
2735
3124
|
this.qstashClient = qstashClient;
|
|
2736
3125
|
this.workflowRunId = workflowRunId;
|
|
@@ -2742,6 +3131,7 @@ var WorkflowContext = class {
|
|
|
2742
3131
|
this.env = env ?? {};
|
|
2743
3132
|
this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
|
|
2744
3133
|
this.retried = retried ?? 0;
|
|
3134
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2745
3135
|
const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
|
|
2746
3136
|
middlewareManagerInstance.assignContext(this);
|
|
2747
3137
|
this.executor = new AutoExecutor(
|
|
@@ -2750,7 +3140,8 @@ var WorkflowContext = class {
|
|
|
2750
3140
|
middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
|
|
2751
3141
|
middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
|
|
2752
3142
|
telemetry,
|
|
2753
|
-
invokeCount
|
|
3143
|
+
invokeCount,
|
|
3144
|
+
this.effectiveConfig
|
|
2754
3145
|
);
|
|
2755
3146
|
}
|
|
2756
3147
|
/**
|
|
@@ -2776,13 +3167,35 @@ var WorkflowContext = class {
|
|
|
2776
3167
|
* ])
|
|
2777
3168
|
* ```
|
|
2778
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
|
+
*
|
|
2779
3186
|
* @param stepName name of the step
|
|
2780
3187
|
* @param stepFunction step function to be executed
|
|
3188
|
+
* @param stepSettings step-level settings for this step
|
|
2781
3189
|
* @returns result of the step function
|
|
2782
3190
|
*/
|
|
2783
|
-
async run(stepName, stepFunction) {
|
|
3191
|
+
async run(stepName, stepFunction, stepSettings) {
|
|
3192
|
+
if (stepSettings) {
|
|
3193
|
+
validateFlowControl(stepSettings.flowControl);
|
|
3194
|
+
}
|
|
2784
3195
|
const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
|
|
2785
|
-
return await this.addStep(
|
|
3196
|
+
return await this.addStep(
|
|
3197
|
+
new LazyFunctionStep(this, stepName, wrappedStepFunction, stepSettings)
|
|
3198
|
+
);
|
|
2786
3199
|
}
|
|
2787
3200
|
/**
|
|
2788
3201
|
* Stops the execution for the duration provided.
|
|
@@ -3005,7 +3418,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3005
3418
|
*
|
|
3006
3419
|
* @param routeFunction
|
|
3007
3420
|
*/
|
|
3008
|
-
static async tryAuthentication(routeFunction, context) {
|
|
3421
|
+
static async tryAuthentication(routeFunction, context, effectiveConfig) {
|
|
3009
3422
|
const disabledContext = new _DisabledWorkflowContext({
|
|
3010
3423
|
qstashClient: new import_qstash10.Client({
|
|
3011
3424
|
baseUrl: "disabled-client",
|
|
@@ -3020,7 +3433,10 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3020
3433
|
initialPayload: context.requestPayload,
|
|
3021
3434
|
env: context.env,
|
|
3022
3435
|
label: context.labels,
|
|
3023
|
-
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
|
|
3024
3440
|
});
|
|
3025
3441
|
try {
|
|
3026
3442
|
await routeFunction(disabledContext);
|
|
@@ -3237,6 +3653,7 @@ var handleFailure = async ({
|
|
|
3237
3653
|
}
|
|
3238
3654
|
const userHeaders = recreateUserHeaders(request.headers);
|
|
3239
3655
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3656
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3240
3657
|
const workflowContext = new WorkflowContext({
|
|
3241
3658
|
qstashClient,
|
|
3242
3659
|
workflowRunId,
|
|
@@ -3250,11 +3667,13 @@ var handleFailure = async ({
|
|
|
3250
3667
|
label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
|
|
3251
3668
|
retried,
|
|
3252
3669
|
workflowRunCreatedAt: workflowCreatedAt,
|
|
3253
|
-
middlewareManager: void 0
|
|
3670
|
+
middlewareManager: void 0,
|
|
3671
|
+
effectiveConfig
|
|
3254
3672
|
});
|
|
3255
3673
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3256
3674
|
routeFunction,
|
|
3257
|
-
workflowContext
|
|
3675
|
+
workflowContext,
|
|
3676
|
+
effectiveConfig
|
|
3258
3677
|
);
|
|
3259
3678
|
if (authCheck.isErr()) {
|
|
3260
3679
|
await dispatchDebug?.("onError", {
|
|
@@ -3346,7 +3765,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
|
|
|
3346
3765
|
const client = new import_qstash11.Client({
|
|
3347
3766
|
...clientOptions,
|
|
3348
3767
|
baseUrl: clientEnv.QSTASH_URL,
|
|
3349
|
-
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)
|
|
3350
3775
|
});
|
|
3351
3776
|
const receiver = getReceiver(environment, receiverConfig, region);
|
|
3352
3777
|
return { client, receiver };
|
|
@@ -3396,7 +3821,9 @@ var getQStashHandlers = ({
|
|
|
3396
3821
|
client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
|
|
3397
3822
|
...qstashClientOption,
|
|
3398
3823
|
baseUrl: environment.QSTASH_URL,
|
|
3399
|
-
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)
|
|
3400
3827
|
}),
|
|
3401
3828
|
receiver: getReceiver(environment, receiverConfig)
|
|
3402
3829
|
}
|
|
@@ -3736,12 +4163,14 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3736
4163
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3737
4164
|
const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
|
|
3738
4165
|
const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
4166
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3739
4167
|
const workflowContext = new WorkflowContext({
|
|
3740
4168
|
qstashClient: regionalClient,
|
|
3741
4169
|
workflowRunId,
|
|
3742
4170
|
initialPayload: isThirdPartyCallResult(request) ? JSON.parse(rawInitialPayload) : initialPayloadParser(rawInitialPayload),
|
|
3743
4171
|
headers: recreateUserHeaders(request.headers),
|
|
3744
4172
|
steps,
|
|
4173
|
+
effectiveConfig,
|
|
3745
4174
|
url: workflowUrl,
|
|
3746
4175
|
env,
|
|
3747
4176
|
telemetry,
|
|
@@ -3753,7 +4182,8 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3753
4182
|
});
|
|
3754
4183
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3755
4184
|
routeFunction,
|
|
3756
|
-
workflowContext
|
|
4185
|
+
workflowContext,
|
|
4186
|
+
effectiveConfig
|
|
3757
4187
|
);
|
|
3758
4188
|
if (authCheck.isErr()) {
|
|
3759
4189
|
throw authCheck.error;
|
|
@@ -3790,7 +4220,23 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3790
4220
|
if (steps.length === 1) {
|
|
3791
4221
|
await middlewareManager.dispatchLifecycle("runStarted", {});
|
|
3792
4222
|
}
|
|
3793
|
-
|
|
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;
|
|
3794
4240
|
},
|
|
3795
4241
|
onCleanup: async (result2) => {
|
|
3796
4242
|
await middlewareManager.dispatchLifecycle("runCompleted", {
|
|
@@ -3871,7 +4317,9 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
3871
4317
|
};
|
|
3872
4318
|
|
|
3873
4319
|
// platforms/solidjs.ts
|
|
4320
|
+
var import_qstash13 = require("@upstash/qstash");
|
|
3874
4321
|
var serve = (routeFunction, options) => {
|
|
4322
|
+
void (0, import_qstash13.startDevServer)();
|
|
3875
4323
|
const telemetry = {
|
|
3876
4324
|
sdk: SDK_TELEMETRY,
|
|
3877
4325
|
framework: "solidjs",
|