@upstash/workflow 1.4.0-rc → 1.4.0
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 +2 -3
- package/astro.d.ts +2 -3
- package/astro.js +164 -575
- package/astro.mjs +1 -1
- package/{chunk-UH6O5IJY.mjs → chunk-DRHEHWMS.mjs} +326 -737
- package/cloudflare.d.mts +2 -3
- package/cloudflare.d.ts +2 -3
- package/cloudflare.js +164 -575
- package/cloudflare.mjs +1 -1
- package/express.d.mts +2 -3
- package/express.d.ts +2 -3
- package/express.js +164 -575
- package/express.mjs +1 -1
- package/h3.d.mts +2 -3
- package/h3.d.ts +2 -3
- package/h3.js +164 -575
- package/h3.mjs +1 -1
- package/hono.d.mts +2 -3
- package/hono.d.ts +2 -3
- package/hono.js +164 -575
- package/hono.mjs +1 -1
- package/index.d.mts +2 -3
- package/index.d.ts +2 -3
- package/index.js +164 -575
- package/index.mjs +1 -1
- package/nextjs.d.mts +2 -3
- package/nextjs.d.ts +2 -3
- package/nextjs.js +164 -575
- package/nextjs.mjs +1 -1
- package/package.json +1 -1
- package/react-router.d.mts +2 -3
- package/react-router.d.ts +2 -3
- package/react-router.js +164 -575
- package/react-router.mjs +1 -1
- package/{serve-many-CbIV7145.d.mts → serve-many-CG3BFvO3.d.mts} +1 -1
- package/{serve-many-BCtZg31b.d.ts → serve-many-iJF1IUXk.d.ts} +1 -1
- package/solidjs.d.mts +1 -2
- package/solidjs.d.ts +1 -2
- package/solidjs.js +164 -575
- package/solidjs.mjs +1 -1
- package/svelte.d.mts +2 -3
- package/svelte.d.ts +2 -3
- package/svelte.js +164 -575
- package/svelte.mjs +1 -1
- package/tanstack.d.mts +2 -3
- package/tanstack.d.ts +2 -3
- package/tanstack.js +164 -575
- package/tanstack.mjs +1 -1
- package/{types-ffLPVG5_.d.ts → types-CekOpKvz.d.mts} +8 -248
- package/{types-ffLPVG5_.d.mts → types-CekOpKvz.d.ts} +8 -248
package/h3.js
CHANGED
|
@@ -526,14 +526,6 @@ var WORKFLOW_CREATED_AT_HEADER = "Upstash-Workflow-CreatedAt";
|
|
|
526
526
|
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
|
|
527
527
|
var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
|
|
528
528
|
var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
|
|
529
|
-
var WORKFLOW_FEATURE_SET = "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig";
|
|
530
|
-
var WORKFLOW_STEP_CONFIG_CALL_TYPE = "stepConfig";
|
|
531
|
-
var WORKFLOW_STEP_CONFIG_HEADER = "Upstash-Workflow-Step-Config";
|
|
532
|
-
var FLOW_CONTROL_KEY_HEADER = "Upstash-Flow-Control-Key";
|
|
533
|
-
var FLOW_CONTROL_VALUE_HEADER = "Upstash-Flow-Control-Value";
|
|
534
|
-
var RETRIES_HEADER = "Upstash-Retries";
|
|
535
|
-
var RETRY_DELAY_HEADER = "Upstash-Retry-Delay";
|
|
536
|
-
var WORKFLOW_STEP_CONFIG_FEATURE = "WF_StepConfig";
|
|
537
529
|
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
|
|
538
530
|
var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
|
|
539
531
|
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
|
|
@@ -551,6 +543,102 @@ var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
|
|
|
551
543
|
var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
|
|
552
544
|
var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
|
|
553
545
|
|
|
546
|
+
// src/context/auto-executor.ts
|
|
547
|
+
var import_qstash5 = require("@upstash/qstash");
|
|
548
|
+
|
|
549
|
+
// src/qstash/headers.ts
|
|
550
|
+
var import_qstash4 = require("@upstash/qstash");
|
|
551
|
+
|
|
552
|
+
// src/utils.ts
|
|
553
|
+
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
554
|
+
var NANOID_LENGTH = 21;
|
|
555
|
+
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
556
|
+
function validateLabel(label) {
|
|
557
|
+
if (label === void 0) return;
|
|
558
|
+
const labels = Array.isArray(label) ? label : [label];
|
|
559
|
+
if (labels.length === 0) {
|
|
560
|
+
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
561
|
+
}
|
|
562
|
+
for (const value of labels) {
|
|
563
|
+
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
564
|
+
throw new WorkflowNonRetryableError(
|
|
565
|
+
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function serializeLabel(label) {
|
|
571
|
+
return Array.isArray(label) ? label.join(",") : label;
|
|
572
|
+
}
|
|
573
|
+
function validateFlowControl(flowControl) {
|
|
574
|
+
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
575
|
+
throw new WorkflowNonRetryableError(
|
|
576
|
+
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function getRandomInt() {
|
|
581
|
+
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
582
|
+
}
|
|
583
|
+
function nanoid(length = NANOID_LENGTH) {
|
|
584
|
+
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
585
|
+
}
|
|
586
|
+
function getWorkflowRunId(id) {
|
|
587
|
+
return `wfr_${id ?? nanoid()}`;
|
|
588
|
+
}
|
|
589
|
+
function decodeBase64(base64) {
|
|
590
|
+
const binString = atob(base64);
|
|
591
|
+
try {
|
|
592
|
+
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
593
|
+
return new TextDecoder().decode(intArray);
|
|
594
|
+
} catch (error) {
|
|
595
|
+
console.warn(
|
|
596
|
+
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
597
|
+
);
|
|
598
|
+
return binString;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function getUserIdFromToken(qstashClient) {
|
|
602
|
+
try {
|
|
603
|
+
const token = qstashClient.token;
|
|
604
|
+
const decodedToken = decodeBase64(token);
|
|
605
|
+
const tokenPayload = JSON.parse(decodedToken);
|
|
606
|
+
const userId = tokenPayload.UserID;
|
|
607
|
+
if (!userId) {
|
|
608
|
+
throw new WorkflowError("QStash token payload does not contain userId");
|
|
609
|
+
}
|
|
610
|
+
return userId;
|
|
611
|
+
} catch (error) {
|
|
612
|
+
throw new WorkflowError(
|
|
613
|
+
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function getQStashUrl(qstashClient) {
|
|
618
|
+
try {
|
|
619
|
+
const requester = qstashClient.http;
|
|
620
|
+
const baseUrl = requester.baseUrl;
|
|
621
|
+
if (!baseUrl) {
|
|
622
|
+
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
623
|
+
}
|
|
624
|
+
return baseUrl;
|
|
625
|
+
} catch (error) {
|
|
626
|
+
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function getEventId() {
|
|
630
|
+
return `evt_${nanoid(15)}`;
|
|
631
|
+
}
|
|
632
|
+
function stringifyBody(body) {
|
|
633
|
+
if (body === void 0) {
|
|
634
|
+
return void 0;
|
|
635
|
+
}
|
|
636
|
+
if (typeof body === "string") {
|
|
637
|
+
return body;
|
|
638
|
+
}
|
|
639
|
+
return JSON.stringify(body);
|
|
640
|
+
}
|
|
641
|
+
|
|
554
642
|
// node_modules/neverthrow/dist/index.es.js
|
|
555
643
|
var defaultErrorConfig = {
|
|
556
644
|
withStackTrace: false
|
|
@@ -966,102 +1054,6 @@ var Err = class {
|
|
|
966
1054
|
};
|
|
967
1055
|
var fromThrowable = Result.fromThrowable;
|
|
968
1056
|
|
|
969
|
-
// src/context/auto-executor.ts
|
|
970
|
-
var import_qstash5 = require("@upstash/qstash");
|
|
971
|
-
|
|
972
|
-
// src/qstash/headers.ts
|
|
973
|
-
var import_qstash4 = require("@upstash/qstash");
|
|
974
|
-
|
|
975
|
-
// src/utils.ts
|
|
976
|
-
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
977
|
-
var NANOID_LENGTH = 21;
|
|
978
|
-
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
979
|
-
function validateLabel(label) {
|
|
980
|
-
if (label === void 0) return;
|
|
981
|
-
const labels = Array.isArray(label) ? label : [label];
|
|
982
|
-
if (labels.length === 0) {
|
|
983
|
-
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
984
|
-
}
|
|
985
|
-
for (const value of labels) {
|
|
986
|
-
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
987
|
-
throw new WorkflowNonRetryableError(
|
|
988
|
-
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
989
|
-
);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
function serializeLabel(label) {
|
|
994
|
-
return Array.isArray(label) ? label.join(",") : label;
|
|
995
|
-
}
|
|
996
|
-
function validateFlowControl(flowControl) {
|
|
997
|
-
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
998
|
-
throw new WorkflowNonRetryableError(
|
|
999
|
-
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
1000
|
-
);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
function getRandomInt() {
|
|
1004
|
-
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
1005
|
-
}
|
|
1006
|
-
function nanoid(length = NANOID_LENGTH) {
|
|
1007
|
-
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
1008
|
-
}
|
|
1009
|
-
function getWorkflowRunId(id) {
|
|
1010
|
-
return `wfr_${id ?? nanoid()}`;
|
|
1011
|
-
}
|
|
1012
|
-
function decodeBase64(base64) {
|
|
1013
|
-
const binString = atob(base64);
|
|
1014
|
-
try {
|
|
1015
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
1016
|
-
return new TextDecoder().decode(intArray);
|
|
1017
|
-
} catch (error) {
|
|
1018
|
-
console.warn(
|
|
1019
|
-
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
1020
|
-
);
|
|
1021
|
-
return binString;
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
function getUserIdFromToken(qstashClient) {
|
|
1025
|
-
try {
|
|
1026
|
-
const token = qstashClient.token;
|
|
1027
|
-
const decodedToken = decodeBase64(token);
|
|
1028
|
-
const tokenPayload = JSON.parse(decodedToken);
|
|
1029
|
-
const userId = tokenPayload.UserID;
|
|
1030
|
-
if (!userId) {
|
|
1031
|
-
throw new WorkflowError("QStash token payload does not contain userId");
|
|
1032
|
-
}
|
|
1033
|
-
return userId;
|
|
1034
|
-
} catch (error) {
|
|
1035
|
-
throw new WorkflowError(
|
|
1036
|
-
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
1037
|
-
);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
function getQStashUrl(qstashClient) {
|
|
1041
|
-
try {
|
|
1042
|
-
const requester = qstashClient.http;
|
|
1043
|
-
const baseUrl = requester.baseUrl;
|
|
1044
|
-
if (!baseUrl) {
|
|
1045
|
-
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
1046
|
-
}
|
|
1047
|
-
return baseUrl;
|
|
1048
|
-
} catch (error) {
|
|
1049
|
-
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
function getEventId() {
|
|
1053
|
-
return `evt_${nanoid(15)}`;
|
|
1054
|
-
}
|
|
1055
|
-
function stringifyBody(body) {
|
|
1056
|
-
if (body === void 0) {
|
|
1057
|
-
return void 0;
|
|
1058
|
-
}
|
|
1059
|
-
if (typeof body === "string") {
|
|
1060
|
-
return body;
|
|
1061
|
-
}
|
|
1062
|
-
return JSON.stringify(body);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
1057
|
// src/types.ts
|
|
1066
1058
|
var StepTypes = [
|
|
1067
1059
|
"Initial",
|
|
@@ -1101,6 +1093,10 @@ var triggerFirstInvocation = async (params) => {
|
|
|
1101
1093
|
workflowConfig: {
|
|
1102
1094
|
workflowRunId: workflowContext.workflowRunId,
|
|
1103
1095
|
workflowUrl: workflowContext.url,
|
|
1096
|
+
// the run doesn't exist yet, so the trigger time is sent when the creation time
|
|
1097
|
+
// is not known. serve and client.trigger set it on the context already, so a
|
|
1098
|
+
// retry of this request carries the same value and is deduplicated.
|
|
1099
|
+
workflowRunCreatedAt: workflowContext.workflowRunCreatedAt || Date.now(),
|
|
1104
1100
|
failureUrl,
|
|
1105
1101
|
retries,
|
|
1106
1102
|
retryDelay,
|
|
@@ -1167,10 +1163,6 @@ var triggerFirstInvocation = async (params) => {
|
|
|
1167
1163
|
return err(error_);
|
|
1168
1164
|
}
|
|
1169
1165
|
};
|
|
1170
|
-
var flushPendingStep = async (workflowContext) => {
|
|
1171
|
-
const { executor } = workflowContext;
|
|
1172
|
-
return await executor.submitPendingStep();
|
|
1173
|
-
};
|
|
1174
1166
|
var triggerRouteFunction = async ({
|
|
1175
1167
|
onCleanup,
|
|
1176
1168
|
onStep,
|
|
@@ -1208,7 +1200,10 @@ var triggerWorkflowDelete = async (workflowContext, result, cancel = false, disp
|
|
|
1208
1200
|
path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
|
|
1209
1201
|
method: "DELETE",
|
|
1210
1202
|
parseResponseAsJson: false,
|
|
1211
|
-
body: JSON.stringify(result)
|
|
1203
|
+
body: JSON.stringify(result),
|
|
1204
|
+
// with the creation time, QStash deletes the run only if it is the same run.
|
|
1205
|
+
// a late delete of a previous run can't delete a new run with the same id.
|
|
1206
|
+
headers: workflowContext.workflowRunCreatedAt ? { [WORKFLOW_CREATED_AT_HEADER]: workflowContext.workflowRunCreatedAt.toString() } : void 0
|
|
1212
1207
|
});
|
|
1213
1208
|
await dispatchDebug?.("onInfo", {
|
|
1214
1209
|
info: `Workflow run ${workflowContext.workflowRunId} deleted from QStash successfully.`
|
|
@@ -1285,6 +1280,7 @@ ${atob(callbackMessage.body ?? "")}`
|
|
|
1285
1280
|
const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
|
|
1286
1281
|
const contentType = request.headers.get("Upstash-Workflow-ContentType");
|
|
1287
1282
|
const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
|
|
1283
|
+
const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
1288
1284
|
if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
|
|
1289
1285
|
throw new Error(
|
|
1290
1286
|
`Missing info in callback message source header: ${JSON.stringify({
|
|
@@ -1302,6 +1298,7 @@ ${atob(callbackMessage.body ?? "")}`
|
|
|
1302
1298
|
workflowConfig: {
|
|
1303
1299
|
workflowRunId,
|
|
1304
1300
|
workflowUrl,
|
|
1301
|
+
workflowRunCreatedAt: Number(workflowRunCreatedAt ?? "0"),
|
|
1305
1302
|
telemetry: telemetry2
|
|
1306
1303
|
},
|
|
1307
1304
|
invokeCount: Number(invokeCount)
|
|
@@ -1378,29 +1375,8 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
|
|
|
1378
1375
|
var BaseLazyStep = class _BaseLazyStep {
|
|
1379
1376
|
stepName;
|
|
1380
1377
|
context;
|
|
1381
|
-
|
|
1382
|
-
* step-level settings (flow control, retries etc.) which override the
|
|
1383
|
-
* settings the workflow run was triggered with, for this step only.
|
|
1384
|
-
*/
|
|
1385
|
-
stepSettings;
|
|
1386
|
-
/**
|
|
1387
|
-
* whether this step's result can be submitted after the route function
|
|
1388
|
-
* has moved on, instead of right away.
|
|
1389
|
-
*
|
|
1390
|
-
* Enabled for steps whose `getResultStep` produces the final `out` —
|
|
1391
|
-
* which is not the same as "needs no server round trip":
|
|
1392
|
-
* `LazyCreateWebhookStep` builds its result in `getBody` and returns
|
|
1393
|
-
* `out: undefined` from `getResultStep`, so deferring it would hand
|
|
1394
|
-
* `undefined` to the route function.
|
|
1395
|
-
*
|
|
1396
|
-
* Deferring lets the route function continue and reveal the next step,
|
|
1397
|
-
* so that step's settings can ride on this step's submission instead
|
|
1398
|
-
* of needing a step config request of their own.
|
|
1399
|
-
*/
|
|
1400
|
-
supportsDeferredSubmission = false;
|
|
1401
|
-
constructor(context, stepName, stepSettings) {
|
|
1378
|
+
constructor(context, stepName) {
|
|
1402
1379
|
this.context = context;
|
|
1403
|
-
this.stepSettings = stepSettings;
|
|
1404
1380
|
if (!stepName) {
|
|
1405
1381
|
throw new WorkflowError(
|
|
1406
1382
|
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
|
|
@@ -1464,18 +1440,13 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1464
1440
|
step.out = JSON.stringify(step.out);
|
|
1465
1441
|
return JSON.stringify(step);
|
|
1466
1442
|
}
|
|
1467
|
-
getHeaders({
|
|
1468
|
-
context,
|
|
1469
|
-
telemetry: telemetry2,
|
|
1470
|
-
invokeCount,
|
|
1471
|
-
step,
|
|
1472
|
-
stepSettings
|
|
1473
|
-
}) {
|
|
1443
|
+
getHeaders({ context, telemetry: telemetry2, invokeCount, step }) {
|
|
1474
1444
|
return getHeaders({
|
|
1475
1445
|
initHeaderValue: "false",
|
|
1476
1446
|
workflowConfig: {
|
|
1477
1447
|
workflowRunId: context.workflowRunId,
|
|
1478
1448
|
workflowUrl: context.url,
|
|
1449
|
+
workflowRunCreatedAt: context.workflowRunCreatedAt,
|
|
1479
1450
|
useJSONContent: false,
|
|
1480
1451
|
telemetry: telemetry2
|
|
1481
1452
|
},
|
|
@@ -1483,8 +1454,7 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1483
1454
|
stepInfo: {
|
|
1484
1455
|
step,
|
|
1485
1456
|
lazyStep: this
|
|
1486
|
-
}
|
|
1487
|
-
stepSettings
|
|
1457
|
+
}
|
|
1488
1458
|
});
|
|
1489
1459
|
}
|
|
1490
1460
|
async submitStep({ context, body, headers }) {
|
|
@@ -1502,9 +1472,8 @@ var LazyFunctionStep = class extends BaseLazyStep {
|
|
|
1502
1472
|
stepFunction;
|
|
1503
1473
|
stepType = "Run";
|
|
1504
1474
|
allowUndefinedOut = true;
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
super(context, stepName, stepSettings);
|
|
1475
|
+
constructor(context, stepName, stepFunction) {
|
|
1476
|
+
super(context, stepName);
|
|
1508
1477
|
this.stepFunction = stepFunction;
|
|
1509
1478
|
}
|
|
1510
1479
|
getPlanStep(concurrent, targetStep) {
|
|
@@ -1534,7 +1503,6 @@ var LazySleepStep = class extends BaseLazyStep {
|
|
|
1534
1503
|
sleep;
|
|
1535
1504
|
stepType = "SleepFor";
|
|
1536
1505
|
allowUndefinedOut = true;
|
|
1537
|
-
supportsDeferredSubmission = true;
|
|
1538
1506
|
constructor(context, stepName, sleep) {
|
|
1539
1507
|
super(context, stepName);
|
|
1540
1508
|
this.sleep = sleep;
|
|
@@ -1574,7 +1542,6 @@ var LazySleepUntilStep = class extends BaseLazyStep {
|
|
|
1574
1542
|
sleepUntil;
|
|
1575
1543
|
stepType = "SleepUntil";
|
|
1576
1544
|
allowUndefinedOut = true;
|
|
1577
|
-
supportsDeferredSubmission = true;
|
|
1578
1545
|
constructor(context, stepName, sleepUntil) {
|
|
1579
1546
|
super(context, stepName);
|
|
1580
1547
|
this.sleepUntil = sleepUntil;
|
|
@@ -1807,6 +1774,8 @@ var LazyWaitEventStep = class extends BaseLazyStep {
|
|
|
1807
1774
|
) : {},
|
|
1808
1775
|
// note: using WORKFLOW_ID_HEADER doesn't work, because Runid -> RunId:
|
|
1809
1776
|
"Upstash-Workflow-Runid": [context.workflowRunId],
|
|
1777
|
+
// same for WORKFLOW_CREATED_AT_HEADER, because Createdat -> CreatedAt:
|
|
1778
|
+
...context.workflowRunCreatedAt ? { "Upstash-Workflow-Createdat": [context.workflowRunCreatedAt.toString()] } : {},
|
|
1810
1779
|
[WORKFLOW_INIT_HEADER]: ["false"],
|
|
1811
1780
|
[WORKFLOW_URL_HEADER]: [context.url],
|
|
1812
1781
|
"Upstash-Workflow-CallType": ["step"]
|
|
@@ -1909,6 +1878,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
|
|
|
1909
1878
|
workflowConfig: {
|
|
1910
1879
|
workflowRunId: context.workflowRunId,
|
|
1911
1880
|
workflowUrl: context.url,
|
|
1881
|
+
workflowRunCreatedAt: context.workflowRunCreatedAt,
|
|
1912
1882
|
telemetry: telemetry2,
|
|
1913
1883
|
useJSONContent: false
|
|
1914
1884
|
},
|
|
@@ -1918,6 +1888,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
|
|
|
1918
1888
|
invokerHeaders[key] = value;
|
|
1919
1889
|
});
|
|
1920
1890
|
invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
|
|
1891
|
+
if (context.workflowRunCreatedAt) {
|
|
1892
|
+
invokerHeaders["Upstash-Workflow-Createdat"] = context.workflowRunCreatedAt.toString();
|
|
1893
|
+
}
|
|
1921
1894
|
const request = {
|
|
1922
1895
|
body: stringifyBody(this.params.body),
|
|
1923
1896
|
headers: Object.fromEntries(
|
|
@@ -1946,6 +1919,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
|
|
|
1946
1919
|
workflowConfig: {
|
|
1947
1920
|
workflowRunId: getWorkflowRunId(workflowRunId),
|
|
1948
1921
|
workflowUrl: newUrl,
|
|
1922
|
+
// the invoked run doesn't exist yet, so the creation time of the invoker run is sent.
|
|
1923
|
+
// it separates the invocations made by different runs of the invoker.
|
|
1924
|
+
workflowRunCreatedAt: context.workflowRunCreatedAt,
|
|
1949
1925
|
retries,
|
|
1950
1926
|
retryDelay,
|
|
1951
1927
|
telemetry: telemetry2,
|
|
@@ -2067,7 +2043,6 @@ var WorkflowHeaders = class {
|
|
|
2067
2043
|
invokeCount;
|
|
2068
2044
|
initHeaderValue;
|
|
2069
2045
|
stepInfo;
|
|
2070
|
-
stepSettings;
|
|
2071
2046
|
headers;
|
|
2072
2047
|
/**
|
|
2073
2048
|
* @param params workflow header parameters
|
|
@@ -2077,20 +2052,17 @@ var WorkflowHeaders = class {
|
|
|
2077
2052
|
workflowConfig,
|
|
2078
2053
|
invokeCount,
|
|
2079
2054
|
initHeaderValue,
|
|
2080
|
-
stepInfo
|
|
2081
|
-
stepSettings
|
|
2055
|
+
stepInfo
|
|
2082
2056
|
}) {
|
|
2083
2057
|
this.userHeaders = userHeaders;
|
|
2084
2058
|
this.workflowConfig = workflowConfig;
|
|
2085
2059
|
this.invokeCount = invokeCount;
|
|
2086
2060
|
this.initHeaderValue = initHeaderValue;
|
|
2087
2061
|
this.stepInfo = stepInfo;
|
|
2088
|
-
this.stepSettings = stepSettings;
|
|
2089
2062
|
this.headers = {
|
|
2090
2063
|
rawHeaders: {},
|
|
2091
2064
|
workflowHeaders: {},
|
|
2092
|
-
failureHeaders: {}
|
|
2093
|
-
stepSettingsHeaders: {}
|
|
2065
|
+
failureHeaders: {}
|
|
2094
2066
|
};
|
|
2095
2067
|
}
|
|
2096
2068
|
getHeaders() {
|
|
@@ -2101,7 +2073,6 @@ var WorkflowHeaders = class {
|
|
|
2101
2073
|
this.addUserHeaders();
|
|
2102
2074
|
this.addInvokeCount();
|
|
2103
2075
|
this.addFailureUrl();
|
|
2104
|
-
this.addStepSettings();
|
|
2105
2076
|
const contentType = this.addContentType();
|
|
2106
2077
|
return this.prefixHeaders(contentType);
|
|
2107
2078
|
}
|
|
@@ -2111,10 +2082,13 @@ var WorkflowHeaders = class {
|
|
|
2111
2082
|
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
|
|
2112
2083
|
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
|
|
2113
2084
|
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
|
|
2114
|
-
[WORKFLOW_FEATURE_HEADER]:
|
|
2085
|
+
[WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig",
|
|
2115
2086
|
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
2116
2087
|
...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
|
|
2117
2088
|
};
|
|
2089
|
+
if (this.workflowConfig.workflowRunCreatedAt) {
|
|
2090
|
+
this.headers.rawHeaders[WORKFLOW_CREATED_AT_HEADER] = this.workflowConfig.workflowRunCreatedAt.toString();
|
|
2091
|
+
}
|
|
2118
2092
|
if (this.stepInfo?.lazyStep.stepType !== "Call") {
|
|
2119
2093
|
this.headers.rawHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
|
|
2120
2094
|
}
|
|
@@ -2189,7 +2163,7 @@ var WorkflowHeaders = class {
|
|
|
2189
2163
|
this.headers.failureHeaders["Workflow-Init"] = "false";
|
|
2190
2164
|
this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
|
|
2191
2165
|
this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
|
|
2192
|
-
this.headers.failureHeaders["Feature-Set"] =
|
|
2166
|
+
this.headers.failureHeaders["Feature-Set"] = "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig";
|
|
2193
2167
|
if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
|
|
2194
2168
|
this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
|
|
2195
2169
|
}
|
|
@@ -2197,42 +2171,6 @@ var WorkflowHeaders = class {
|
|
|
2197
2171
|
this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
|
|
2198
2172
|
}
|
|
2199
2173
|
}
|
|
2200
|
-
/**
|
|
2201
|
-
* Applies the step-level settings of a step to the message.
|
|
2202
|
-
*
|
|
2203
|
-
* These headers configure the message itself, so that QStash uses them
|
|
2204
|
-
* instead of the settings the run was triggered with. The delivery of
|
|
2205
|
-
* the message is what executes the step, which is how a step's
|
|
2206
|
-
* settings reach it.
|
|
2207
|
-
*
|
|
2208
|
-
* When any setting is present the feature set is extended with
|
|
2209
|
-
* `WF_StepConfig`, which is what tells QStash to keep them. QStash
|
|
2210
|
-
* reports that back on the delivery as `Upstash-Workflow-Step-Config`.
|
|
2211
|
-
*/
|
|
2212
|
-
addStepSettings() {
|
|
2213
|
-
if (!this.stepSettings) {
|
|
2214
|
-
return;
|
|
2215
|
-
}
|
|
2216
|
-
const headers = {};
|
|
2217
|
-
if (this.stepSettings.flowControl) {
|
|
2218
|
-
const { flowControlKey, flowControlValue } = prepareFlowControl(
|
|
2219
|
-
this.stepSettings.flowControl
|
|
2220
|
-
);
|
|
2221
|
-
headers[FLOW_CONTROL_KEY_HEADER] = flowControlKey;
|
|
2222
|
-
headers[FLOW_CONTROL_VALUE_HEADER] = flowControlValue;
|
|
2223
|
-
}
|
|
2224
|
-
if (this.stepSettings.retries !== void 0) {
|
|
2225
|
-
headers[RETRIES_HEADER] = this.stepSettings.retries.toString();
|
|
2226
|
-
}
|
|
2227
|
-
if (this.stepSettings.retryDelay) {
|
|
2228
|
-
headers[RETRY_DELAY_HEADER] = this.stepSettings.retryDelay;
|
|
2229
|
-
}
|
|
2230
|
-
if (Object.keys(headers).length === 0) {
|
|
2231
|
-
return;
|
|
2232
|
-
}
|
|
2233
|
-
headers[WORKFLOW_FEATURE_HEADER] = `${WORKFLOW_FEATURE_SET},${WORKFLOW_STEP_CONFIG_FEATURE}`;
|
|
2234
|
-
this.headers.stepSettingsHeaders = headers;
|
|
2235
|
-
}
|
|
2236
2174
|
addContentType() {
|
|
2237
2175
|
if (this.workflowConfig.useJSONContent) {
|
|
2238
2176
|
this.headers.rawHeaders["content-type"] = "application/json";
|
|
@@ -2246,17 +2184,14 @@ var WorkflowHeaders = class {
|
|
|
2246
2184
|
return contentType;
|
|
2247
2185
|
}
|
|
2248
2186
|
prefixHeaders(contentType) {
|
|
2249
|
-
const { rawHeaders, workflowHeaders, failureHeaders
|
|
2187
|
+
const { rawHeaders, workflowHeaders, failureHeaders } = this.headers;
|
|
2250
2188
|
const isCall = this.stepInfo?.lazyStep.stepType === "Call";
|
|
2251
2189
|
return {
|
|
2252
2190
|
headers: {
|
|
2253
2191
|
...rawHeaders,
|
|
2254
2192
|
...addPrefixToHeaders(workflowHeaders, isCall ? "Upstash-Callback-" : "Upstash-"),
|
|
2255
2193
|
...addPrefixToHeaders(failureHeaders, "Upstash-Failure-Callback-"),
|
|
2256
|
-
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {}
|
|
2257
|
-
// last, so they override the run's settings above. Never
|
|
2258
|
-
// prefixed: they configure this message, not its callback.
|
|
2259
|
-
...stepSettingsHeaders
|
|
2194
|
+
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {}
|
|
2260
2195
|
},
|
|
2261
2196
|
contentType
|
|
2262
2197
|
};
|
|
@@ -2307,16 +2242,16 @@ var submitParallelSteps = async ({
|
|
|
2307
2242
|
info: `Submitting ${planSteps.length} parallel steps.`
|
|
2308
2243
|
});
|
|
2309
2244
|
const result = await context.qstashClient.batch(
|
|
2310
|
-
planSteps.map((planStep
|
|
2245
|
+
planSteps.map((planStep) => {
|
|
2311
2246
|
const { headers } = getHeaders({
|
|
2312
2247
|
initHeaderValue: "false",
|
|
2313
2248
|
workflowConfig: {
|
|
2314
2249
|
workflowRunId: context.workflowRunId,
|
|
2315
2250
|
workflowUrl: context.url,
|
|
2251
|
+
workflowRunCreatedAt: context.workflowRunCreatedAt,
|
|
2316
2252
|
telemetry: telemetry2
|
|
2317
2253
|
},
|
|
2318
|
-
invokeCount
|
|
2319
|
-
stepSettings: steps[index].stepSettings
|
|
2254
|
+
invokeCount
|
|
2320
2255
|
});
|
|
2321
2256
|
return {
|
|
2322
2257
|
headers,
|
|
@@ -2335,38 +2270,31 @@ var submitParallelSteps = async ({
|
|
|
2335
2270
|
}
|
|
2336
2271
|
throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
|
|
2337
2272
|
};
|
|
2338
|
-
var
|
|
2273
|
+
var submitSingleStep = async ({
|
|
2274
|
+
context,
|
|
2339
2275
|
lazyStep,
|
|
2340
2276
|
stepId,
|
|
2277
|
+
invokeCount,
|
|
2341
2278
|
concurrency,
|
|
2279
|
+
telemetry: telemetry2,
|
|
2280
|
+
dispatchDebug,
|
|
2342
2281
|
dispatchLifecycle
|
|
2343
2282
|
}) => {
|
|
2344
2283
|
await dispatchLifecycle("beforeExecution", {
|
|
2345
2284
|
stepName: lazyStep.stepName
|
|
2346
2285
|
});
|
|
2286
|
+
let resultStep;
|
|
2347
2287
|
try {
|
|
2348
|
-
|
|
2288
|
+
resultStep = await lazyStep.getResultStep(concurrency, stepId);
|
|
2349
2289
|
} catch (error) {
|
|
2350
2290
|
attachStepNameToError(error, lazyStep.stepName);
|
|
2351
2291
|
throw error;
|
|
2352
2292
|
}
|
|
2353
|
-
};
|
|
2354
|
-
var submitStepResult = async ({
|
|
2355
|
-
context,
|
|
2356
|
-
lazyStep,
|
|
2357
|
-
resultStep,
|
|
2358
|
-
invokeCount,
|
|
2359
|
-
concurrency,
|
|
2360
|
-
telemetry: telemetry2,
|
|
2361
|
-
dispatchDebug,
|
|
2362
|
-
nextStepSettings
|
|
2363
|
-
}) => {
|
|
2364
2293
|
const { headers } = lazyStep.getHeaders({
|
|
2365
2294
|
context,
|
|
2366
2295
|
step: resultStep,
|
|
2367
2296
|
invokeCount,
|
|
2368
|
-
telemetry: telemetry2
|
|
2369
|
-
stepSettings: nextStepSettings
|
|
2297
|
+
telemetry: telemetry2
|
|
2370
2298
|
});
|
|
2371
2299
|
const body = lazyStep.getBody({
|
|
2372
2300
|
context,
|
|
@@ -2391,138 +2319,6 @@ var submitStepResult = async ({
|
|
|
2391
2319
|
}
|
|
2392
2320
|
return resultStep;
|
|
2393
2321
|
};
|
|
2394
|
-
var publishStepConfigRequest = async ({
|
|
2395
|
-
context,
|
|
2396
|
-
lazyStep,
|
|
2397
|
-
targetStep,
|
|
2398
|
-
invokeCount,
|
|
2399
|
-
telemetry: telemetry2,
|
|
2400
|
-
dispatchDebug
|
|
2401
|
-
}) => {
|
|
2402
|
-
const { headers } = getHeaders({
|
|
2403
|
-
initHeaderValue: "false",
|
|
2404
|
-
workflowConfig: {
|
|
2405
|
-
workflowRunId: context.workflowRunId,
|
|
2406
|
-
workflowUrl: context.url,
|
|
2407
|
-
telemetry: telemetry2
|
|
2408
|
-
},
|
|
2409
|
-
invokeCount,
|
|
2410
|
-
stepSettings: lazyStep.stepSettings
|
|
2411
|
-
});
|
|
2412
|
-
await dispatchDebug("onInfo", {
|
|
2413
|
-
info: `Step "${lazyStep.stepName}" (${targetStep}) has step-level settings which the current delivery does not carry. Requesting a delivery which does.`
|
|
2414
|
-
});
|
|
2415
|
-
const result = await context.qstashClient.publishJSON({
|
|
2416
|
-
headers: {
|
|
2417
|
-
...headers,
|
|
2418
|
-
"Upstash-Workflow-CallType": WORKFLOW_STEP_CONFIG_CALL_TYPE
|
|
2419
|
-
},
|
|
2420
|
-
method: "POST",
|
|
2421
|
-
body: { targetStep, invokeCount },
|
|
2422
|
-
url: context.url,
|
|
2423
|
-
contentBasedDeduplication: true
|
|
2424
|
-
});
|
|
2425
|
-
if (result?.deduplicated) {
|
|
2426
|
-
await dispatchDebug("onWarning", {
|
|
2427
|
-
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.`
|
|
2428
|
-
});
|
|
2429
|
-
} else if (result?.messageId) {
|
|
2430
|
-
await dispatchDebug("onInfo", {
|
|
2431
|
-
info: `Requested a delivery under the settings of step "${lazyStep.stepName}" with messageId: ${result.messageId}.`
|
|
2432
|
-
});
|
|
2433
|
-
}
|
|
2434
|
-
};
|
|
2435
|
-
|
|
2436
|
-
// src/qstash/step-config.ts
|
|
2437
|
-
var DEFAULT_PARALLELISM = 0;
|
|
2438
|
-
var DEFAULT_RATE = 0;
|
|
2439
|
-
var DEFAULT_PERIOD_SECONDS = 1;
|
|
2440
|
-
var SECONDS_PER_UNIT = {
|
|
2441
|
-
s: 1,
|
|
2442
|
-
m: 60,
|
|
2443
|
-
h: 60 * 60,
|
|
2444
|
-
d: 24 * 60 * 60
|
|
2445
|
-
};
|
|
2446
|
-
var durationToSeconds = (duration) => {
|
|
2447
|
-
if (typeof duration === "number") {
|
|
2448
|
-
return Number.isFinite(duration) ? duration : void 0;
|
|
2449
|
-
}
|
|
2450
|
-
const match = /^(\d+)([smhd])$/.exec(duration.trim());
|
|
2451
|
-
if (match) {
|
|
2452
|
-
return Number(match[1]) * SECONDS_PER_UNIT[match[2]];
|
|
2453
|
-
}
|
|
2454
|
-
const seconds = Number(duration.trim());
|
|
2455
|
-
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2456
|
-
};
|
|
2457
|
-
var normalizeFlowControl = (flowControl) => {
|
|
2458
|
-
const period = flowControl.period === void 0 ? void 0 : durationToSeconds(flowControl.period);
|
|
2459
|
-
return {
|
|
2460
|
-
key: flowControl.key,
|
|
2461
|
-
parallelism: flowControl.parallelism ?? DEFAULT_PARALLELISM,
|
|
2462
|
-
rate: flowControl.rate ?? flowControl.ratePerSecond ?? DEFAULT_RATE,
|
|
2463
|
-
period: period ?? DEFAULT_PERIOD_SECONDS
|
|
2464
|
-
};
|
|
2465
|
-
};
|
|
2466
|
-
var parseFlowControlHeaders = (key, value) => {
|
|
2467
|
-
if (!key) {
|
|
2468
|
-
return void 0;
|
|
2469
|
-
}
|
|
2470
|
-
const flowControl = {
|
|
2471
|
-
key,
|
|
2472
|
-
parallelism: DEFAULT_PARALLELISM,
|
|
2473
|
-
rate: DEFAULT_RATE,
|
|
2474
|
-
period: DEFAULT_PERIOD_SECONDS
|
|
2475
|
-
};
|
|
2476
|
-
for (const entry of (value ?? "").split(",")) {
|
|
2477
|
-
const [name, rawValue] = entry.split("=").map((part) => part.trim());
|
|
2478
|
-
if (!name || rawValue === void 0) {
|
|
2479
|
-
continue;
|
|
2480
|
-
}
|
|
2481
|
-
switch (name) {
|
|
2482
|
-
case "parallelism": {
|
|
2483
|
-
flowControl.parallelism = Number(rawValue);
|
|
2484
|
-
break;
|
|
2485
|
-
}
|
|
2486
|
-
case "rate": {
|
|
2487
|
-
flowControl.rate = Number(rawValue);
|
|
2488
|
-
break;
|
|
2489
|
-
}
|
|
2490
|
-
case "period": {
|
|
2491
|
-
flowControl.period = durationToSeconds(rawValue) ?? DEFAULT_PERIOD_SECONDS;
|
|
2492
|
-
break;
|
|
2493
|
-
}
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
return flowControl;
|
|
2497
|
-
};
|
|
2498
|
-
var getEffectiveConfig = (headers) => {
|
|
2499
|
-
const retriesHeader = headers.get(RETRIES_HEADER);
|
|
2500
|
-
return {
|
|
2501
|
-
flowControl: parseFlowControlHeaders(
|
|
2502
|
-
headers.get(FLOW_CONTROL_KEY_HEADER),
|
|
2503
|
-
headers.get(FLOW_CONTROL_VALUE_HEADER)
|
|
2504
|
-
),
|
|
2505
|
-
retries: retriesHeader === null ? void 0 : Number(retriesHeader),
|
|
2506
|
-
retryDelay: headers.get(RETRY_DELAY_HEADER) ?? void 0,
|
|
2507
|
-
hasStepConfig: headers.get(WORKFLOW_STEP_CONFIG_HEADER) === "true"
|
|
2508
|
-
};
|
|
2509
|
-
};
|
|
2510
|
-
var describeStepSettingsMismatch = (stepSettings, effectiveConfig) => {
|
|
2511
|
-
if (stepSettings.flowControl) {
|
|
2512
|
-
const wanted = normalizeFlowControl(stepSettings.flowControl);
|
|
2513
|
-
const applied = effectiveConfig.flowControl;
|
|
2514
|
-
if (!applied || applied.key !== wanted.key || applied.parallelism !== wanted.parallelism || applied.rate !== wanted.rate || applied.period !== wanted.period) {
|
|
2515
|
-
return `flow control: expected ${JSON.stringify(wanted)}, delivery has ${JSON.stringify(applied)}`;
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
if (stepSettings.retries !== void 0 && effectiveConfig.retries !== void 0 && stepSettings.retries !== effectiveConfig.retries) {
|
|
2519
|
-
return `retries: expected ${stepSettings.retries}, delivery has ${effectiveConfig.retries}`;
|
|
2520
|
-
}
|
|
2521
|
-
if (stepSettings.retryDelay !== void 0 && stepSettings.retryDelay !== effectiveConfig.retryDelay) {
|
|
2522
|
-
return `retry delay: expected '${stepSettings.retryDelay}', delivery has '${effectiveConfig.retryDelay ?? ""}'`;
|
|
2523
|
-
}
|
|
2524
|
-
return void 0;
|
|
2525
|
-
};
|
|
2526
2322
|
|
|
2527
2323
|
// src/context/auto-executor.ts
|
|
2528
2324
|
var AutoExecutor = class _AutoExecutor {
|
|
@@ -2539,25 +2335,6 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2539
2335
|
stepCount = 0;
|
|
2540
2336
|
planStepCount = 0;
|
|
2541
2337
|
executingStep = false;
|
|
2542
|
-
/**
|
|
2543
|
-
* an executed step whose result hasn't been submitted to QStash yet.
|
|
2544
|
-
*
|
|
2545
|
-
* Set when a step which can produce its result in-process executes. The
|
|
2546
|
-
* submission waits so that the route function can continue and reveal
|
|
2547
|
-
* what comes next: if that is a single step with step-level settings,
|
|
2548
|
-
* the settings ride on this submission and no step config request is
|
|
2549
|
-
* needed, since the delivery of the submission is what executes it.
|
|
2550
|
-
*
|
|
2551
|
-
* Flushed when the next step is reached (see `addStep`) or when the
|
|
2552
|
-
* route function ends (see `flushPendingStep` in `serve`).
|
|
2553
|
-
*/
|
|
2554
|
-
pendingStep;
|
|
2555
|
-
/**
|
|
2556
|
-
* configuration QStash applied to the delivery being handled. A step
|
|
2557
|
-
* with step-level settings is settled by comparing its settings against
|
|
2558
|
-
* this.
|
|
2559
|
-
*/
|
|
2560
|
-
effectiveConfig;
|
|
2561
2338
|
/**
|
|
2562
2339
|
* @param context workflow context
|
|
2563
2340
|
* @param steps list of steps
|
|
@@ -2565,17 +2342,14 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2565
2342
|
* @param dispatchLifecycle lifecycle event dispatcher
|
|
2566
2343
|
* @param telemetry optional telemetry information
|
|
2567
2344
|
* @param invokeCount optional invoke count
|
|
2568
|
-
* @param effectiveConfig configuration QStash applied to the delivery
|
|
2569
|
-
* being handled
|
|
2570
2345
|
*/
|
|
2571
|
-
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry2, invokeCount
|
|
2346
|
+
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry2, invokeCount) {
|
|
2572
2347
|
this.context = context;
|
|
2573
2348
|
this.steps = steps;
|
|
2574
2349
|
this.dispatchDebug = dispatchDebug;
|
|
2575
2350
|
this.dispatchLifecycle = dispatchLifecycle;
|
|
2576
2351
|
this.telemetry = telemetry2;
|
|
2577
2352
|
this.invokeCount = invokeCount ?? 0;
|
|
2578
|
-
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2579
2353
|
this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
|
|
2580
2354
|
}
|
|
2581
2355
|
/**
|
|
@@ -2610,15 +2384,6 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2610
2384
|
lazyStepList.push(stepInfo);
|
|
2611
2385
|
const index = this.indexInCurrentList++;
|
|
2612
2386
|
const requestComplete = this.deferExecution().then(async () => {
|
|
2613
|
-
const submitted = await this.submitPendingStep(
|
|
2614
|
-
lazyStepList.length === 1 ? lazyStepList[0].stepSettings : void 0
|
|
2615
|
-
);
|
|
2616
|
-
if (submitted.isErr()) {
|
|
2617
|
-
throw submitted.error;
|
|
2618
|
-
}
|
|
2619
|
-
if (submitted.value.result === "submitted-step") {
|
|
2620
|
-
throw submitted.value.abort;
|
|
2621
|
-
}
|
|
2622
2387
|
if (!this.promises.has(lazyStepList)) {
|
|
2623
2388
|
const promise2 = this.getExecutionPromise(lazyStepList);
|
|
2624
2389
|
this.promises.set(lazyStepList, promise2);
|
|
@@ -2672,100 +2437,18 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2672
2437
|
}
|
|
2673
2438
|
return parsedOut;
|
|
2674
2439
|
}
|
|
2675
|
-
const
|
|
2676
|
-
if (mismatch) {
|
|
2677
|
-
if (!this.effectiveConfig.hasStepConfig) {
|
|
2678
|
-
await publishStepConfigRequest({
|
|
2679
|
-
context: this.context,
|
|
2680
|
-
lazyStep,
|
|
2681
|
-
targetStep: this.stepCount,
|
|
2682
|
-
invokeCount: this.invokeCount,
|
|
2683
|
-
telemetry: this.telemetry,
|
|
2684
|
-
dispatchDebug: this.dispatchDebug
|
|
2685
|
-
});
|
|
2686
|
-
throw new WorkflowAbort(lazyStep.stepName);
|
|
2687
|
-
} else {
|
|
2688
|
-
await this.dispatchDebug("onWarning", {
|
|
2689
|
-
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.`
|
|
2690
|
-
});
|
|
2691
|
-
}
|
|
2692
|
-
}
|
|
2693
|
-
const resultStep = await executeStep({
|
|
2694
|
-
lazyStep,
|
|
2695
|
-
stepId: this.stepCount,
|
|
2696
|
-
concurrency: NO_CONCURRENCY,
|
|
2697
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2698
|
-
});
|
|
2699
|
-
if (lazyStep.supportsDeferredSubmission) {
|
|
2700
|
-
this.pendingStep = { status: "held", lazyStep, resultStep };
|
|
2701
|
-
return lazyStep.parseOut({
|
|
2702
|
-
...resultStep,
|
|
2703
|
-
out: resultStep.out === void 0 ? void 0 : JSON.stringify(resultStep.out)
|
|
2704
|
-
});
|
|
2705
|
-
}
|
|
2706
|
-
await submitStepResult({
|
|
2440
|
+
const resultStep = await submitSingleStep({
|
|
2707
2441
|
context: this.context,
|
|
2708
2442
|
lazyStep,
|
|
2709
|
-
|
|
2443
|
+
stepId: this.stepCount,
|
|
2710
2444
|
invokeCount: this.invokeCount,
|
|
2711
|
-
concurrency:
|
|
2445
|
+
concurrency: 1,
|
|
2712
2446
|
telemetry: this.telemetry,
|
|
2713
|
-
dispatchDebug: this.dispatchDebug
|
|
2447
|
+
dispatchDebug: this.dispatchDebug,
|
|
2448
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2714
2449
|
});
|
|
2715
2450
|
throw new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2716
2451
|
}
|
|
2717
|
-
/**
|
|
2718
|
-
* Submits the result of the step being held, if there is one, and
|
|
2719
|
-
* returns the abort the caller has to throw to end the invocation.
|
|
2720
|
-
*
|
|
2721
|
-
* Returns undefined when no step is being held, which is the caller's
|
|
2722
|
-
* signal that there is nothing to end. Throws instead of returning
|
|
2723
|
-
* when the submission itself fails, so that error reaches the caller
|
|
2724
|
-
* rather than an abort claiming the step was submitted.
|
|
2725
|
-
*
|
|
2726
|
-
* Callers which arrive while a submission is already under way (the
|
|
2727
|
-
* steps of a parallel group, added together) wait for that one and get
|
|
2728
|
-
* the same abort, rather than submitting a second time.
|
|
2729
|
-
*
|
|
2730
|
-
* The next step's settings are attached whenever it has any, without
|
|
2731
|
-
* checking them against the current delivery: the executor only knows
|
|
2732
|
-
* the configuration of the delivery in hand, which inside a
|
|
2733
|
-
* delivery is the *previous* step's, so such a check would be wrong
|
|
2734
|
-
* exactly when two steps with settings follow each other.
|
|
2735
|
-
*
|
|
2736
|
-
* @param nextStepSettings step-level settings of the next step
|
|
2737
|
-
* @returns `submitted-step` with the abort which ends this invocation,
|
|
2738
|
-
* or `no-pending-step` when nothing was held and there is nothing to
|
|
2739
|
-
* end
|
|
2740
|
-
*/
|
|
2741
|
-
async submitPendingStep(nextStepSettings) {
|
|
2742
|
-
if (!this.pendingStep) {
|
|
2743
|
-
return ok({ result: "no-pending-step" });
|
|
2744
|
-
}
|
|
2745
|
-
try {
|
|
2746
|
-
if (this.pendingStep.status === "submitting") {
|
|
2747
|
-
return ok({ result: "submitted-step", abort: await this.pendingStep.submitted });
|
|
2748
|
-
}
|
|
2749
|
-
const { lazyStep, resultStep } = this.pendingStep;
|
|
2750
|
-
const submitted = (async () => {
|
|
2751
|
-
await submitStepResult({
|
|
2752
|
-
context: this.context,
|
|
2753
|
-
lazyStep,
|
|
2754
|
-
resultStep,
|
|
2755
|
-
invokeCount: this.invokeCount,
|
|
2756
|
-
concurrency: NO_CONCURRENCY,
|
|
2757
|
-
telemetry: this.telemetry,
|
|
2758
|
-
dispatchDebug: this.dispatchDebug,
|
|
2759
|
-
nextStepSettings
|
|
2760
|
-
});
|
|
2761
|
-
return new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2762
|
-
})();
|
|
2763
|
-
this.pendingStep = { status: "submitting", submitted };
|
|
2764
|
-
return ok({ result: "submitted-step", abort: await submitted });
|
|
2765
|
-
} catch (error) {
|
|
2766
|
-
return err(error);
|
|
2767
|
-
}
|
|
2768
|
-
}
|
|
2769
2452
|
/**
|
|
2770
2453
|
* Runs steps in parallel.
|
|
2771
2454
|
*
|
|
@@ -2814,20 +2497,15 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2814
2497
|
validateStep(parallelSteps[stepIndex], planStep);
|
|
2815
2498
|
try {
|
|
2816
2499
|
const parallelStep = parallelSteps[stepIndex];
|
|
2817
|
-
const resultStep = await
|
|
2818
|
-
lazyStep: parallelStep,
|
|
2819
|
-
stepId: planStep.targetStep,
|
|
2820
|
-
concurrency: parallelSteps.length,
|
|
2821
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2822
|
-
});
|
|
2823
|
-
await submitStepResult({
|
|
2500
|
+
const resultStep = await submitSingleStep({
|
|
2824
2501
|
context: this.context,
|
|
2825
2502
|
lazyStep: parallelStep,
|
|
2826
|
-
|
|
2503
|
+
stepId: planStep.targetStep,
|
|
2827
2504
|
invokeCount: this.invokeCount,
|
|
2828
2505
|
concurrency: parallelSteps.length,
|
|
2829
2506
|
telemetry: this.telemetry,
|
|
2830
|
-
dispatchDebug: this.dispatchDebug
|
|
2507
|
+
dispatchDebug: this.dispatchDebug,
|
|
2508
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2831
2509
|
});
|
|
2832
2510
|
throw new WorkflowAbort(parallelStep.stepName, resultStep);
|
|
2833
2511
|
} catch (error) {
|
|
@@ -3443,46 +3121,6 @@ var WorkflowContext = class {
|
|
|
3443
3121
|
* `1` on the first retry, `2` on the second, and so on.
|
|
3444
3122
|
*/
|
|
3445
3123
|
retried;
|
|
3446
|
-
/**
|
|
3447
|
-
* Configuration QStash applied to the request being handled.
|
|
3448
|
-
*
|
|
3449
|
-
* This is the configuration of *this delivery*, not of the run: it is
|
|
3450
|
-
* the configuration the run was triggered with, except inside the
|
|
3451
|
-
* delivery which executes a step that has step-level settings, where
|
|
3452
|
-
* it is that step's settings. The fields below expose it; the executor
|
|
3453
|
-
* gets it directly rather than reaching through the context.
|
|
3454
|
-
*/
|
|
3455
|
-
effectiveConfig;
|
|
3456
|
-
/**
|
|
3457
|
-
* Flow control QStash applied to the request being handled, if any.
|
|
3458
|
-
*
|
|
3459
|
-
* This is the flow control of the request in hand, which is the one the
|
|
3460
|
-
* run was triggered with except inside a step carrying its own.
|
|
3461
|
-
*/
|
|
3462
|
-
get flowControl() {
|
|
3463
|
-
return this.effectiveConfig.flowControl;
|
|
3464
|
-
}
|
|
3465
|
-
/**
|
|
3466
|
-
* Retry limit QStash applied to the request being handled, if it
|
|
3467
|
-
* reported one.
|
|
3468
|
-
*
|
|
3469
|
-
* Not to be confused with {@link retried}, which is how many retries
|
|
3470
|
-
* have already happened.
|
|
3471
|
-
*
|
|
3472
|
-
* @see {@link flowControl} for what "the request being handled" means
|
|
3473
|
-
*/
|
|
3474
|
-
get retries() {
|
|
3475
|
-
return this.effectiveConfig.retries;
|
|
3476
|
-
}
|
|
3477
|
-
/**
|
|
3478
|
-
* Retry delay expression QStash applied to the request being handled,
|
|
3479
|
-
* if any.
|
|
3480
|
-
*
|
|
3481
|
-
* @see {@link flowControl} for what "the request being handled" means
|
|
3482
|
-
*/
|
|
3483
|
-
get retryDelay() {
|
|
3484
|
-
return this.effectiveConfig.retryDelay;
|
|
3485
|
-
}
|
|
3486
3124
|
constructor({
|
|
3487
3125
|
qstashClient,
|
|
3488
3126
|
workflowRunId,
|
|
@@ -3496,8 +3134,7 @@ var WorkflowContext = class {
|
|
|
3496
3134
|
invokeCount,
|
|
3497
3135
|
label,
|
|
3498
3136
|
retried,
|
|
3499
|
-
middlewareManager
|
|
3500
|
-
effectiveConfig
|
|
3137
|
+
middlewareManager
|
|
3501
3138
|
}) {
|
|
3502
3139
|
this.qstashClient = qstashClient;
|
|
3503
3140
|
this.workflowRunId = workflowRunId;
|
|
@@ -3509,7 +3146,6 @@ var WorkflowContext = class {
|
|
|
3509
3146
|
this.env = env ?? {};
|
|
3510
3147
|
this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
|
|
3511
3148
|
this.retried = retried ?? 0;
|
|
3512
|
-
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
3513
3149
|
const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
|
|
3514
3150
|
middlewareManagerInstance.assignContext(this);
|
|
3515
3151
|
this.executor = new AutoExecutor(
|
|
@@ -3518,8 +3154,7 @@ var WorkflowContext = class {
|
|
|
3518
3154
|
middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
|
|
3519
3155
|
middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
|
|
3520
3156
|
telemetry2,
|
|
3521
|
-
invokeCount
|
|
3522
|
-
this.effectiveConfig
|
|
3157
|
+
invokeCount
|
|
3523
3158
|
);
|
|
3524
3159
|
}
|
|
3525
3160
|
/**
|
|
@@ -3545,35 +3180,13 @@ var WorkflowContext = class {
|
|
|
3545
3180
|
* ])
|
|
3546
3181
|
* ```
|
|
3547
3182
|
*
|
|
3548
|
-
* Step-level settings can be passed as a third argument, overriding
|
|
3549
|
-
* the settings the workflow run was triggered with for this step only:
|
|
3550
|
-
*
|
|
3551
|
-
* ```typescript
|
|
3552
|
-
* const result = await context.run(
|
|
3553
|
-
* "step 1",
|
|
3554
|
-
* () => {
|
|
3555
|
-
* return "result"
|
|
3556
|
-
* },
|
|
3557
|
-
* {
|
|
3558
|
-
* flowControl: { key: "custom-key", parallelism: 3 },
|
|
3559
|
-
* retries: 5,
|
|
3560
|
-
* }
|
|
3561
|
-
* )
|
|
3562
|
-
* ```
|
|
3563
|
-
*
|
|
3564
3183
|
* @param stepName name of the step
|
|
3565
3184
|
* @param stepFunction step function to be executed
|
|
3566
|
-
* @param stepSettings step-level settings for this step
|
|
3567
3185
|
* @returns result of the step function
|
|
3568
3186
|
*/
|
|
3569
|
-
async run(stepName, stepFunction
|
|
3570
|
-
if (stepSettings) {
|
|
3571
|
-
validateFlowControl(stepSettings.flowControl);
|
|
3572
|
-
}
|
|
3187
|
+
async run(stepName, stepFunction) {
|
|
3573
3188
|
const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
|
|
3574
|
-
return await this.addStep(
|
|
3575
|
-
new LazyFunctionStep(this, stepName, wrappedStepFunction, stepSettings)
|
|
3576
|
-
);
|
|
3189
|
+
return await this.addStep(new LazyFunctionStep(this, stepName, wrappedStepFunction));
|
|
3577
3190
|
}
|
|
3578
3191
|
/**
|
|
3579
3192
|
* Stops the execution for the duration provided.
|
|
@@ -3796,7 +3409,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3796
3409
|
*
|
|
3797
3410
|
* @param routeFunction
|
|
3798
3411
|
*/
|
|
3799
|
-
static async tryAuthentication(routeFunction, context
|
|
3412
|
+
static async tryAuthentication(routeFunction, context) {
|
|
3800
3413
|
const disabledContext = new _DisabledWorkflowContext({
|
|
3801
3414
|
qstashClient: new import_qstash10.Client({
|
|
3802
3415
|
baseUrl: "disabled-client",
|
|
@@ -3811,10 +3424,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3811
3424
|
initialPayload: context.requestPayload,
|
|
3812
3425
|
env: context.env,
|
|
3813
3426
|
label: context.labels,
|
|
3814
|
-
retried: context.retried
|
|
3815
|
-
// the route function runs for real here until it reaches a step, so
|
|
3816
|
-
// it observes the same configuration as it will on the real context
|
|
3817
|
-
effectiveConfig
|
|
3427
|
+
retried: context.retried
|
|
3818
3428
|
});
|
|
3819
3429
|
try {
|
|
3820
3430
|
await routeFunction(disabledContext);
|
|
@@ -4031,7 +3641,6 @@ var handleFailure = async ({
|
|
|
4031
3641
|
}
|
|
4032
3642
|
const userHeaders = recreateUserHeaders(request.headers);
|
|
4033
3643
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
4034
|
-
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
4035
3644
|
const workflowContext = new WorkflowContext({
|
|
4036
3645
|
qstashClient,
|
|
4037
3646
|
workflowRunId,
|
|
@@ -4045,13 +3654,11 @@ var handleFailure = async ({
|
|
|
4045
3654
|
label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
|
|
4046
3655
|
retried,
|
|
4047
3656
|
workflowRunCreatedAt: workflowCreatedAt,
|
|
4048
|
-
middlewareManager: void 0
|
|
4049
|
-
effectiveConfig
|
|
3657
|
+
middlewareManager: void 0
|
|
4050
3658
|
});
|
|
4051
3659
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
4052
3660
|
routeFunction,
|
|
4053
|
-
workflowContext
|
|
4054
|
-
effectiveConfig
|
|
3661
|
+
workflowContext
|
|
4055
3662
|
);
|
|
4056
3663
|
if (authCheck.isErr()) {
|
|
4057
3664
|
await dispatchDebug?.("onError", {
|
|
@@ -4540,28 +4147,26 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
4540
4147
|
const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
|
|
4541
4148
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
4542
4149
|
const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
|
|
4543
|
-
const
|
|
4544
|
-
const
|
|
4150
|
+
const workflowRunCreatedAtHeader = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
4151
|
+
const workflowRunCreatedAt = workflowRunCreatedAtHeader ? Number(workflowRunCreatedAtHeader) : isFirstInvocation ? Date.now() : 0;
|
|
4545
4152
|
const workflowContext = new WorkflowContext({
|
|
4546
4153
|
qstashClient: regionalClient,
|
|
4547
4154
|
workflowRunId,
|
|
4548
4155
|
initialPayload: isThirdPartyCallResult(request) ? JSON.parse(rawInitialPayload) : initialPayloadParser(rawInitialPayload),
|
|
4549
4156
|
headers: recreateUserHeaders(request.headers),
|
|
4550
4157
|
steps,
|
|
4551
|
-
effectiveConfig,
|
|
4552
4158
|
url: workflowUrl,
|
|
4553
4159
|
env,
|
|
4554
4160
|
telemetry: telemetry2,
|
|
4555
4161
|
invokeCount,
|
|
4556
4162
|
label,
|
|
4557
4163
|
retried,
|
|
4558
|
-
workflowRunCreatedAt
|
|
4164
|
+
workflowRunCreatedAt,
|
|
4559
4165
|
middlewareManager
|
|
4560
4166
|
});
|
|
4561
4167
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
4562
4168
|
routeFunction,
|
|
4563
|
-
workflowContext
|
|
4564
|
-
effectiveConfig
|
|
4169
|
+
workflowContext
|
|
4565
4170
|
);
|
|
4566
4171
|
if (authCheck.isErr()) {
|
|
4567
4172
|
throw authCheck.error;
|
|
@@ -4598,23 +4203,7 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
4598
4203
|
if (steps.length === 1) {
|
|
4599
4204
|
await middlewareManager.dispatchLifecycle("runStarted", {});
|
|
4600
4205
|
}
|
|
4601
|
-
|
|
4602
|
-
try {
|
|
4603
|
-
outcome = { ran: true, result: await routeFunction(workflowContext) };
|
|
4604
|
-
} catch (error) {
|
|
4605
|
-
outcome = { ran: false, error };
|
|
4606
|
-
}
|
|
4607
|
-
const submitted = await flushPendingStep(workflowContext);
|
|
4608
|
-
if (submitted.isErr()) {
|
|
4609
|
-
throw submitted.error;
|
|
4610
|
-
}
|
|
4611
|
-
if (submitted.value.result === "submitted-step") {
|
|
4612
|
-
throw submitted.value.abort;
|
|
4613
|
-
}
|
|
4614
|
-
if (!outcome.ran) {
|
|
4615
|
-
throw outcome.error;
|
|
4616
|
-
}
|
|
4617
|
-
return outcome.result;
|
|
4206
|
+
return await routeFunction(workflowContext);
|
|
4618
4207
|
},
|
|
4619
4208
|
onCleanup: async (result2) => {
|
|
4620
4209
|
await middlewareManager.dispatchLifecycle("runCompleted", {
|