@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/h3.js
CHANGED
|
@@ -335,6 +335,9 @@ async function _callHandler(event, handler, hooks) {
|
|
|
335
335
|
var H3Headers = globalThis.Headers;
|
|
336
336
|
var H3Response = globalThis.Response;
|
|
337
337
|
|
|
338
|
+
// src/serve/index.ts
|
|
339
|
+
var import_qstash12 = require("@upstash/qstash");
|
|
340
|
+
|
|
338
341
|
// src/client/utils.ts
|
|
339
342
|
var import_qstash2 = require("@upstash/qstash");
|
|
340
343
|
|
|
@@ -523,6 +526,14 @@ var WORKFLOW_CREATED_AT_HEADER = "Upstash-Workflow-CreatedAt";
|
|
|
523
526
|
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
|
|
524
527
|
var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
|
|
525
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";
|
|
526
537
|
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
|
|
527
538
|
var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
|
|
528
539
|
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
|
|
@@ -540,102 +551,6 @@ var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
|
|
|
540
551
|
var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
|
|
541
552
|
var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
|
|
542
553
|
|
|
543
|
-
// src/context/auto-executor.ts
|
|
544
|
-
var import_qstash5 = require("@upstash/qstash");
|
|
545
|
-
|
|
546
|
-
// src/qstash/headers.ts
|
|
547
|
-
var import_qstash4 = require("@upstash/qstash");
|
|
548
|
-
|
|
549
|
-
// src/utils.ts
|
|
550
|
-
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
551
|
-
var NANOID_LENGTH = 21;
|
|
552
|
-
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
553
|
-
function validateLabel(label) {
|
|
554
|
-
if (label === void 0) return;
|
|
555
|
-
const labels = Array.isArray(label) ? label : [label];
|
|
556
|
-
if (labels.length === 0) {
|
|
557
|
-
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
558
|
-
}
|
|
559
|
-
for (const value of labels) {
|
|
560
|
-
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
561
|
-
throw new WorkflowNonRetryableError(
|
|
562
|
-
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
function serializeLabel(label) {
|
|
568
|
-
return Array.isArray(label) ? label.join(",") : label;
|
|
569
|
-
}
|
|
570
|
-
function validateFlowControl(flowControl) {
|
|
571
|
-
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
572
|
-
throw new WorkflowNonRetryableError(
|
|
573
|
-
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
function getRandomInt() {
|
|
578
|
-
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
579
|
-
}
|
|
580
|
-
function nanoid(length = NANOID_LENGTH) {
|
|
581
|
-
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
582
|
-
}
|
|
583
|
-
function getWorkflowRunId(id) {
|
|
584
|
-
return `wfr_${id ?? nanoid()}`;
|
|
585
|
-
}
|
|
586
|
-
function decodeBase64(base64) {
|
|
587
|
-
const binString = atob(base64);
|
|
588
|
-
try {
|
|
589
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
590
|
-
return new TextDecoder().decode(intArray);
|
|
591
|
-
} catch (error) {
|
|
592
|
-
console.warn(
|
|
593
|
-
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
594
|
-
);
|
|
595
|
-
return binString;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
function getUserIdFromToken(qstashClient) {
|
|
599
|
-
try {
|
|
600
|
-
const token = qstashClient.token;
|
|
601
|
-
const decodedToken = decodeBase64(token);
|
|
602
|
-
const tokenPayload = JSON.parse(decodedToken);
|
|
603
|
-
const userId = tokenPayload.UserID;
|
|
604
|
-
if (!userId) {
|
|
605
|
-
throw new WorkflowError("QStash token payload does not contain userId");
|
|
606
|
-
}
|
|
607
|
-
return userId;
|
|
608
|
-
} catch (error) {
|
|
609
|
-
throw new WorkflowError(
|
|
610
|
-
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
611
|
-
);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
function getQStashUrl(qstashClient) {
|
|
615
|
-
try {
|
|
616
|
-
const requester = qstashClient.http;
|
|
617
|
-
const baseUrl = requester.baseUrl;
|
|
618
|
-
if (!baseUrl) {
|
|
619
|
-
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
620
|
-
}
|
|
621
|
-
return baseUrl;
|
|
622
|
-
} catch (error) {
|
|
623
|
-
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
function getEventId() {
|
|
627
|
-
return `evt_${nanoid(15)}`;
|
|
628
|
-
}
|
|
629
|
-
function stringifyBody(body) {
|
|
630
|
-
if (body === void 0) {
|
|
631
|
-
return void 0;
|
|
632
|
-
}
|
|
633
|
-
if (typeof body === "string") {
|
|
634
|
-
return body;
|
|
635
|
-
}
|
|
636
|
-
return JSON.stringify(body);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
554
|
// node_modules/neverthrow/dist/index.es.js
|
|
640
555
|
var defaultErrorConfig = {
|
|
641
556
|
withStackTrace: false
|
|
@@ -1051,6 +966,102 @@ var Err = class {
|
|
|
1051
966
|
};
|
|
1052
967
|
var fromThrowable = Result.fromThrowable;
|
|
1053
968
|
|
|
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
|
+
|
|
1054
1065
|
// src/types.ts
|
|
1055
1066
|
var StepTypes = [
|
|
1056
1067
|
"Initial",
|
|
@@ -1156,6 +1167,10 @@ var triggerFirstInvocation = async (params) => {
|
|
|
1156
1167
|
return err(error_);
|
|
1157
1168
|
}
|
|
1158
1169
|
};
|
|
1170
|
+
var flushPendingStep = async (workflowContext) => {
|
|
1171
|
+
const { executor } = workflowContext;
|
|
1172
|
+
return await executor.submitPendingStep();
|
|
1173
|
+
};
|
|
1159
1174
|
var triggerRouteFunction = async ({
|
|
1160
1175
|
onCleanup,
|
|
1161
1176
|
onStep,
|
|
@@ -1363,8 +1378,29 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
|
|
|
1363
1378
|
var BaseLazyStep = class _BaseLazyStep {
|
|
1364
1379
|
stepName;
|
|
1365
1380
|
context;
|
|
1366
|
-
|
|
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) {
|
|
1367
1402
|
this.context = context;
|
|
1403
|
+
this.stepSettings = stepSettings;
|
|
1368
1404
|
if (!stepName) {
|
|
1369
1405
|
throw new WorkflowError(
|
|
1370
1406
|
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
|
|
@@ -1428,7 +1464,13 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1428
1464
|
step.out = JSON.stringify(step.out);
|
|
1429
1465
|
return JSON.stringify(step);
|
|
1430
1466
|
}
|
|
1431
|
-
getHeaders({
|
|
1467
|
+
getHeaders({
|
|
1468
|
+
context,
|
|
1469
|
+
telemetry: telemetry2,
|
|
1470
|
+
invokeCount,
|
|
1471
|
+
step,
|
|
1472
|
+
stepSettings
|
|
1473
|
+
}) {
|
|
1432
1474
|
return getHeaders({
|
|
1433
1475
|
initHeaderValue: "false",
|
|
1434
1476
|
workflowConfig: {
|
|
@@ -1441,7 +1483,8 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1441
1483
|
stepInfo: {
|
|
1442
1484
|
step,
|
|
1443
1485
|
lazyStep: this
|
|
1444
|
-
}
|
|
1486
|
+
},
|
|
1487
|
+
stepSettings
|
|
1445
1488
|
});
|
|
1446
1489
|
}
|
|
1447
1490
|
async submitStep({ context, body, headers }) {
|
|
@@ -1459,8 +1502,9 @@ var LazyFunctionStep = class extends BaseLazyStep {
|
|
|
1459
1502
|
stepFunction;
|
|
1460
1503
|
stepType = "Run";
|
|
1461
1504
|
allowUndefinedOut = true;
|
|
1462
|
-
|
|
1463
|
-
|
|
1505
|
+
supportsDeferredSubmission = true;
|
|
1506
|
+
constructor(context, stepName, stepFunction, stepSettings) {
|
|
1507
|
+
super(context, stepName, stepSettings);
|
|
1464
1508
|
this.stepFunction = stepFunction;
|
|
1465
1509
|
}
|
|
1466
1510
|
getPlanStep(concurrent, targetStep) {
|
|
@@ -1490,6 +1534,7 @@ var LazySleepStep = class extends BaseLazyStep {
|
|
|
1490
1534
|
sleep;
|
|
1491
1535
|
stepType = "SleepFor";
|
|
1492
1536
|
allowUndefinedOut = true;
|
|
1537
|
+
supportsDeferredSubmission = true;
|
|
1493
1538
|
constructor(context, stepName, sleep) {
|
|
1494
1539
|
super(context, stepName);
|
|
1495
1540
|
this.sleep = sleep;
|
|
@@ -1529,6 +1574,7 @@ var LazySleepUntilStep = class extends BaseLazyStep {
|
|
|
1529
1574
|
sleepUntil;
|
|
1530
1575
|
stepType = "SleepUntil";
|
|
1531
1576
|
allowUndefinedOut = true;
|
|
1577
|
+
supportsDeferredSubmission = true;
|
|
1532
1578
|
constructor(context, stepName, sleepUntil) {
|
|
1533
1579
|
super(context, stepName);
|
|
1534
1580
|
this.sleepUntil = sleepUntil;
|
|
@@ -2021,6 +2067,7 @@ var WorkflowHeaders = class {
|
|
|
2021
2067
|
invokeCount;
|
|
2022
2068
|
initHeaderValue;
|
|
2023
2069
|
stepInfo;
|
|
2070
|
+
stepSettings;
|
|
2024
2071
|
headers;
|
|
2025
2072
|
/**
|
|
2026
2073
|
* @param params workflow header parameters
|
|
@@ -2030,17 +2077,20 @@ var WorkflowHeaders = class {
|
|
|
2030
2077
|
workflowConfig,
|
|
2031
2078
|
invokeCount,
|
|
2032
2079
|
initHeaderValue,
|
|
2033
|
-
stepInfo
|
|
2080
|
+
stepInfo,
|
|
2081
|
+
stepSettings
|
|
2034
2082
|
}) {
|
|
2035
2083
|
this.userHeaders = userHeaders;
|
|
2036
2084
|
this.workflowConfig = workflowConfig;
|
|
2037
2085
|
this.invokeCount = invokeCount;
|
|
2038
2086
|
this.initHeaderValue = initHeaderValue;
|
|
2039
2087
|
this.stepInfo = stepInfo;
|
|
2088
|
+
this.stepSettings = stepSettings;
|
|
2040
2089
|
this.headers = {
|
|
2041
2090
|
rawHeaders: {},
|
|
2042
2091
|
workflowHeaders: {},
|
|
2043
|
-
failureHeaders: {}
|
|
2092
|
+
failureHeaders: {},
|
|
2093
|
+
stepSettingsHeaders: {}
|
|
2044
2094
|
};
|
|
2045
2095
|
}
|
|
2046
2096
|
getHeaders() {
|
|
@@ -2051,6 +2101,7 @@ var WorkflowHeaders = class {
|
|
|
2051
2101
|
this.addUserHeaders();
|
|
2052
2102
|
this.addInvokeCount();
|
|
2053
2103
|
this.addFailureUrl();
|
|
2104
|
+
this.addStepSettings();
|
|
2054
2105
|
const contentType = this.addContentType();
|
|
2055
2106
|
return this.prefixHeaders(contentType);
|
|
2056
2107
|
}
|
|
@@ -2060,7 +2111,7 @@ var WorkflowHeaders = class {
|
|
|
2060
2111
|
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
|
|
2061
2112
|
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
|
|
2062
2113
|
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
|
|
2063
|
-
[WORKFLOW_FEATURE_HEADER]:
|
|
2114
|
+
[WORKFLOW_FEATURE_HEADER]: WORKFLOW_FEATURE_SET,
|
|
2064
2115
|
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
2065
2116
|
...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
|
|
2066
2117
|
};
|
|
@@ -2138,7 +2189,7 @@ var WorkflowHeaders = class {
|
|
|
2138
2189
|
this.headers.failureHeaders["Workflow-Init"] = "false";
|
|
2139
2190
|
this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
|
|
2140
2191
|
this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
|
|
2141
|
-
this.headers.failureHeaders["Feature-Set"] =
|
|
2192
|
+
this.headers.failureHeaders["Feature-Set"] = WORKFLOW_FEATURE_SET;
|
|
2142
2193
|
if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
|
|
2143
2194
|
this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
|
|
2144
2195
|
}
|
|
@@ -2146,6 +2197,42 @@ var WorkflowHeaders = class {
|
|
|
2146
2197
|
this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
|
|
2147
2198
|
}
|
|
2148
2199
|
}
|
|
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
|
+
}
|
|
2149
2236
|
addContentType() {
|
|
2150
2237
|
if (this.workflowConfig.useJSONContent) {
|
|
2151
2238
|
this.headers.rawHeaders["content-type"] = "application/json";
|
|
@@ -2159,14 +2246,17 @@ var WorkflowHeaders = class {
|
|
|
2159
2246
|
return contentType;
|
|
2160
2247
|
}
|
|
2161
2248
|
prefixHeaders(contentType) {
|
|
2162
|
-
const { rawHeaders, workflowHeaders, failureHeaders } = this.headers;
|
|
2249
|
+
const { rawHeaders, workflowHeaders, failureHeaders, stepSettingsHeaders } = this.headers;
|
|
2163
2250
|
const isCall = this.stepInfo?.lazyStep.stepType === "Call";
|
|
2164
2251
|
return {
|
|
2165
2252
|
headers: {
|
|
2166
2253
|
...rawHeaders,
|
|
2167
2254
|
...addPrefixToHeaders(workflowHeaders, isCall ? "Upstash-Callback-" : "Upstash-"),
|
|
2168
2255
|
...addPrefixToHeaders(failureHeaders, "Upstash-Failure-Callback-"),
|
|
2169
|
-
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-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
|
|
2170
2260
|
},
|
|
2171
2261
|
contentType
|
|
2172
2262
|
};
|
|
@@ -2217,7 +2307,7 @@ var submitParallelSteps = async ({
|
|
|
2217
2307
|
info: `Submitting ${planSteps.length} parallel steps.`
|
|
2218
2308
|
});
|
|
2219
2309
|
const result = await context.qstashClient.batch(
|
|
2220
|
-
planSteps.map((planStep) => {
|
|
2310
|
+
planSteps.map((planStep, index) => {
|
|
2221
2311
|
const { headers } = getHeaders({
|
|
2222
2312
|
initHeaderValue: "false",
|
|
2223
2313
|
workflowConfig: {
|
|
@@ -2225,7 +2315,8 @@ var submitParallelSteps = async ({
|
|
|
2225
2315
|
workflowUrl: context.url,
|
|
2226
2316
|
telemetry: telemetry2
|
|
2227
2317
|
},
|
|
2228
|
-
invokeCount
|
|
2318
|
+
invokeCount,
|
|
2319
|
+
stepSettings: steps[index].stepSettings
|
|
2229
2320
|
});
|
|
2230
2321
|
return {
|
|
2231
2322
|
headers,
|
|
@@ -2244,31 +2335,38 @@ var submitParallelSteps = async ({
|
|
|
2244
2335
|
}
|
|
2245
2336
|
throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
|
|
2246
2337
|
};
|
|
2247
|
-
var
|
|
2248
|
-
context,
|
|
2338
|
+
var executeStep = async ({
|
|
2249
2339
|
lazyStep,
|
|
2250
2340
|
stepId,
|
|
2251
|
-
invokeCount,
|
|
2252
2341
|
concurrency,
|
|
2253
|
-
telemetry: telemetry2,
|
|
2254
|
-
dispatchDebug,
|
|
2255
2342
|
dispatchLifecycle
|
|
2256
2343
|
}) => {
|
|
2257
2344
|
await dispatchLifecycle("beforeExecution", {
|
|
2258
2345
|
stepName: lazyStep.stepName
|
|
2259
2346
|
});
|
|
2260
|
-
let resultStep;
|
|
2261
2347
|
try {
|
|
2262
|
-
|
|
2348
|
+
return await lazyStep.getResultStep(concurrency, stepId);
|
|
2263
2349
|
} catch (error) {
|
|
2264
2350
|
attachStepNameToError(error, lazyStep.stepName);
|
|
2265
2351
|
throw error;
|
|
2266
2352
|
}
|
|
2353
|
+
};
|
|
2354
|
+
var submitStepResult = async ({
|
|
2355
|
+
context,
|
|
2356
|
+
lazyStep,
|
|
2357
|
+
resultStep,
|
|
2358
|
+
invokeCount,
|
|
2359
|
+
concurrency,
|
|
2360
|
+
telemetry: telemetry2,
|
|
2361
|
+
dispatchDebug,
|
|
2362
|
+
nextStepSettings
|
|
2363
|
+
}) => {
|
|
2267
2364
|
const { headers } = lazyStep.getHeaders({
|
|
2268
2365
|
context,
|
|
2269
2366
|
step: resultStep,
|
|
2270
2367
|
invokeCount,
|
|
2271
|
-
telemetry: telemetry2
|
|
2368
|
+
telemetry: telemetry2,
|
|
2369
|
+
stepSettings: nextStepSettings
|
|
2272
2370
|
});
|
|
2273
2371
|
const body = lazyStep.getBody({
|
|
2274
2372
|
context,
|
|
@@ -2293,6 +2391,138 @@ var submitSingleStep = async ({
|
|
|
2293
2391
|
}
|
|
2294
2392
|
return resultStep;
|
|
2295
2393
|
};
|
|
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
|
+
};
|
|
2296
2526
|
|
|
2297
2527
|
// src/context/auto-executor.ts
|
|
2298
2528
|
var AutoExecutor = class _AutoExecutor {
|
|
@@ -2309,6 +2539,25 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2309
2539
|
stepCount = 0;
|
|
2310
2540
|
planStepCount = 0;
|
|
2311
2541
|
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;
|
|
2312
2561
|
/**
|
|
2313
2562
|
* @param context workflow context
|
|
2314
2563
|
* @param steps list of steps
|
|
@@ -2316,14 +2565,17 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2316
2565
|
* @param dispatchLifecycle lifecycle event dispatcher
|
|
2317
2566
|
* @param telemetry optional telemetry information
|
|
2318
2567
|
* @param invokeCount optional invoke count
|
|
2568
|
+
* @param effectiveConfig configuration QStash applied to the delivery
|
|
2569
|
+
* being handled
|
|
2319
2570
|
*/
|
|
2320
|
-
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry2, invokeCount) {
|
|
2571
|
+
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry2, invokeCount, effectiveConfig) {
|
|
2321
2572
|
this.context = context;
|
|
2322
2573
|
this.steps = steps;
|
|
2323
2574
|
this.dispatchDebug = dispatchDebug;
|
|
2324
2575
|
this.dispatchLifecycle = dispatchLifecycle;
|
|
2325
2576
|
this.telemetry = telemetry2;
|
|
2326
2577
|
this.invokeCount = invokeCount ?? 0;
|
|
2578
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2327
2579
|
this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
|
|
2328
2580
|
}
|
|
2329
2581
|
/**
|
|
@@ -2358,6 +2610,15 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2358
2610
|
lazyStepList.push(stepInfo);
|
|
2359
2611
|
const index = this.indexInCurrentList++;
|
|
2360
2612
|
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
|
+
}
|
|
2361
2622
|
if (!this.promises.has(lazyStepList)) {
|
|
2362
2623
|
const promise2 = this.getExecutionPromise(lazyStepList);
|
|
2363
2624
|
this.promises.set(lazyStepList, promise2);
|
|
@@ -2411,18 +2672,100 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2411
2672
|
}
|
|
2412
2673
|
return parsedOut;
|
|
2413
2674
|
}
|
|
2414
|
-
const
|
|
2415
|
-
|
|
2675
|
+
const mismatch = lazyStep.stepSettings ? describeStepSettingsMismatch(lazyStep.stepSettings, this.effectiveConfig) : void 0;
|
|
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({
|
|
2416
2694
|
lazyStep,
|
|
2417
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({
|
|
2707
|
+
context: this.context,
|
|
2708
|
+
lazyStep,
|
|
2709
|
+
resultStep,
|
|
2418
2710
|
invokeCount: this.invokeCount,
|
|
2419
|
-
concurrency:
|
|
2711
|
+
concurrency: NO_CONCURRENCY,
|
|
2420
2712
|
telemetry: this.telemetry,
|
|
2421
|
-
dispatchDebug: this.dispatchDebug
|
|
2422
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2713
|
+
dispatchDebug: this.dispatchDebug
|
|
2423
2714
|
});
|
|
2424
2715
|
throw new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2425
2716
|
}
|
|
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
|
+
}
|
|
2426
2769
|
/**
|
|
2427
2770
|
* Runs steps in parallel.
|
|
2428
2771
|
*
|
|
@@ -2471,15 +2814,20 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2471
2814
|
validateStep(parallelSteps[stepIndex], planStep);
|
|
2472
2815
|
try {
|
|
2473
2816
|
const parallelStep = parallelSteps[stepIndex];
|
|
2474
|
-
const resultStep = await
|
|
2475
|
-
context: this.context,
|
|
2817
|
+
const resultStep = await executeStep({
|
|
2476
2818
|
lazyStep: parallelStep,
|
|
2477
2819
|
stepId: planStep.targetStep,
|
|
2820
|
+
concurrency: parallelSteps.length,
|
|
2821
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2822
|
+
});
|
|
2823
|
+
await submitStepResult({
|
|
2824
|
+
context: this.context,
|
|
2825
|
+
lazyStep: parallelStep,
|
|
2826
|
+
resultStep,
|
|
2478
2827
|
invokeCount: this.invokeCount,
|
|
2479
2828
|
concurrency: parallelSteps.length,
|
|
2480
2829
|
telemetry: this.telemetry,
|
|
2481
|
-
dispatchDebug: this.dispatchDebug
|
|
2482
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2830
|
+
dispatchDebug: this.dispatchDebug
|
|
2483
2831
|
});
|
|
2484
2832
|
throw new WorkflowAbort(parallelStep.stepName, resultStep);
|
|
2485
2833
|
} catch (error) {
|
|
@@ -3095,6 +3443,46 @@ var WorkflowContext = class {
|
|
|
3095
3443
|
* `1` on the first retry, `2` on the second, and so on.
|
|
3096
3444
|
*/
|
|
3097
3445
|
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
|
+
}
|
|
3098
3486
|
constructor({
|
|
3099
3487
|
qstashClient,
|
|
3100
3488
|
workflowRunId,
|
|
@@ -3108,7 +3496,8 @@ var WorkflowContext = class {
|
|
|
3108
3496
|
invokeCount,
|
|
3109
3497
|
label,
|
|
3110
3498
|
retried,
|
|
3111
|
-
middlewareManager
|
|
3499
|
+
middlewareManager,
|
|
3500
|
+
effectiveConfig
|
|
3112
3501
|
}) {
|
|
3113
3502
|
this.qstashClient = qstashClient;
|
|
3114
3503
|
this.workflowRunId = workflowRunId;
|
|
@@ -3120,6 +3509,7 @@ var WorkflowContext = class {
|
|
|
3120
3509
|
this.env = env ?? {};
|
|
3121
3510
|
this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
|
|
3122
3511
|
this.retried = retried ?? 0;
|
|
3512
|
+
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
3123
3513
|
const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
|
|
3124
3514
|
middlewareManagerInstance.assignContext(this);
|
|
3125
3515
|
this.executor = new AutoExecutor(
|
|
@@ -3128,7 +3518,8 @@ var WorkflowContext = class {
|
|
|
3128
3518
|
middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
|
|
3129
3519
|
middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
|
|
3130
3520
|
telemetry2,
|
|
3131
|
-
invokeCount
|
|
3521
|
+
invokeCount,
|
|
3522
|
+
this.effectiveConfig
|
|
3132
3523
|
);
|
|
3133
3524
|
}
|
|
3134
3525
|
/**
|
|
@@ -3154,13 +3545,35 @@ var WorkflowContext = class {
|
|
|
3154
3545
|
* ])
|
|
3155
3546
|
* ```
|
|
3156
3547
|
*
|
|
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
|
+
*
|
|
3157
3564
|
* @param stepName name of the step
|
|
3158
3565
|
* @param stepFunction step function to be executed
|
|
3566
|
+
* @param stepSettings step-level settings for this step
|
|
3159
3567
|
* @returns result of the step function
|
|
3160
3568
|
*/
|
|
3161
|
-
async run(stepName, stepFunction) {
|
|
3569
|
+
async run(stepName, stepFunction, stepSettings) {
|
|
3570
|
+
if (stepSettings) {
|
|
3571
|
+
validateFlowControl(stepSettings.flowControl);
|
|
3572
|
+
}
|
|
3162
3573
|
const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
|
|
3163
|
-
return await this.addStep(
|
|
3574
|
+
return await this.addStep(
|
|
3575
|
+
new LazyFunctionStep(this, stepName, wrappedStepFunction, stepSettings)
|
|
3576
|
+
);
|
|
3164
3577
|
}
|
|
3165
3578
|
/**
|
|
3166
3579
|
* Stops the execution for the duration provided.
|
|
@@ -3383,7 +3796,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3383
3796
|
*
|
|
3384
3797
|
* @param routeFunction
|
|
3385
3798
|
*/
|
|
3386
|
-
static async tryAuthentication(routeFunction, context) {
|
|
3799
|
+
static async tryAuthentication(routeFunction, context, effectiveConfig) {
|
|
3387
3800
|
const disabledContext = new _DisabledWorkflowContext({
|
|
3388
3801
|
qstashClient: new import_qstash10.Client({
|
|
3389
3802
|
baseUrl: "disabled-client",
|
|
@@ -3398,7 +3811,10 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3398
3811
|
initialPayload: context.requestPayload,
|
|
3399
3812
|
env: context.env,
|
|
3400
3813
|
label: context.labels,
|
|
3401
|
-
retried: context.retried
|
|
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
|
|
3402
3818
|
});
|
|
3403
3819
|
try {
|
|
3404
3820
|
await routeFunction(disabledContext);
|
|
@@ -3615,6 +4031,7 @@ var handleFailure = async ({
|
|
|
3615
4031
|
}
|
|
3616
4032
|
const userHeaders = recreateUserHeaders(request.headers);
|
|
3617
4033
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
4034
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3618
4035
|
const workflowContext = new WorkflowContext({
|
|
3619
4036
|
qstashClient,
|
|
3620
4037
|
workflowRunId,
|
|
@@ -3628,11 +4045,13 @@ var handleFailure = async ({
|
|
|
3628
4045
|
label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
|
|
3629
4046
|
retried,
|
|
3630
4047
|
workflowRunCreatedAt: workflowCreatedAt,
|
|
3631
|
-
middlewareManager: void 0
|
|
4048
|
+
middlewareManager: void 0,
|
|
4049
|
+
effectiveConfig
|
|
3632
4050
|
});
|
|
3633
4051
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3634
4052
|
routeFunction,
|
|
3635
|
-
workflowContext
|
|
4053
|
+
workflowContext,
|
|
4054
|
+
effectiveConfig
|
|
3636
4055
|
);
|
|
3637
4056
|
if (authCheck.isErr()) {
|
|
3638
4057
|
await dispatchDebug?.("onError", {
|
|
@@ -3724,7 +4143,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
|
|
|
3724
4143
|
const client = new import_qstash11.Client({
|
|
3725
4144
|
...clientOptions,
|
|
3726
4145
|
baseUrl: clientEnv.QSTASH_URL,
|
|
3727
|
-
token: clientEnv.QSTASH_TOKEN
|
|
4146
|
+
token: clientEnv.QSTASH_TOKEN,
|
|
4147
|
+
// Mirror the receiver's dev-mode decision so a single QSTASH_DEV=true points
|
|
4148
|
+
// the client at the local dev server too. Without this the QStash Client
|
|
4149
|
+
// re-derives dev mode from its own process.env, which is blind to the
|
|
4150
|
+
// Cloudflare worker `env` binding that actually carries QSTASH_DEV there.
|
|
4151
|
+
// An explicit devMode in the user's client config still wins.
|
|
4152
|
+
devMode: clientOptions?.devMode ?? isQStashDevModeEnabled(environment)
|
|
3728
4153
|
});
|
|
3729
4154
|
const receiver = getReceiver(environment, receiverConfig, region);
|
|
3730
4155
|
return { client, receiver };
|
|
@@ -3774,7 +4199,9 @@ var getQStashHandlers = ({
|
|
|
3774
4199
|
client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
|
|
3775
4200
|
...qstashClientOption,
|
|
3776
4201
|
baseUrl: environment.QSTASH_URL,
|
|
3777
|
-
token: environment.QSTASH_TOKEN
|
|
4202
|
+
token: environment.QSTASH_TOKEN,
|
|
4203
|
+
// Mirror the receiver's dev-mode decision (see createRegionalHandler).
|
|
4204
|
+
devMode: qstashClientOption?.devMode ?? isQStashDevModeEnabled(environment)
|
|
3778
4205
|
}),
|
|
3779
4206
|
receiver: getReceiver(environment, receiverConfig)
|
|
3780
4207
|
}
|
|
@@ -4114,12 +4541,14 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
4114
4541
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
4115
4542
|
const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
|
|
4116
4543
|
const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
4544
|
+
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
4117
4545
|
const workflowContext = new WorkflowContext({
|
|
4118
4546
|
qstashClient: regionalClient,
|
|
4119
4547
|
workflowRunId,
|
|
4120
4548
|
initialPayload: isThirdPartyCallResult(request) ? JSON.parse(rawInitialPayload) : initialPayloadParser(rawInitialPayload),
|
|
4121
4549
|
headers: recreateUserHeaders(request.headers),
|
|
4122
4550
|
steps,
|
|
4551
|
+
effectiveConfig,
|
|
4123
4552
|
url: workflowUrl,
|
|
4124
4553
|
env,
|
|
4125
4554
|
telemetry: telemetry2,
|
|
@@ -4131,7 +4560,8 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
4131
4560
|
});
|
|
4132
4561
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
4133
4562
|
routeFunction,
|
|
4134
|
-
workflowContext
|
|
4563
|
+
workflowContext,
|
|
4564
|
+
effectiveConfig
|
|
4135
4565
|
);
|
|
4136
4566
|
if (authCheck.isErr()) {
|
|
4137
4567
|
throw authCheck.error;
|
|
@@ -4168,7 +4598,23 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
4168
4598
|
if (steps.length === 1) {
|
|
4169
4599
|
await middlewareManager.dispatchLifecycle("runStarted", {});
|
|
4170
4600
|
}
|
|
4171
|
-
|
|
4601
|
+
let outcome;
|
|
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;
|
|
4172
4618
|
},
|
|
4173
4619
|
onCleanup: async (result2) => {
|
|
4174
4620
|
await middlewareManager.dispatchLifecycle("runCompleted", {
|
|
@@ -4249,6 +4695,7 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
|
4249
4695
|
};
|
|
4250
4696
|
|
|
4251
4697
|
// platforms/h3.ts
|
|
4698
|
+
var import_qstash13 = require("@upstash/qstash");
|
|
4252
4699
|
function transformHeaders(headers) {
|
|
4253
4700
|
const formattedHeaders = Object.entries(headers).map(([key, value]) => [
|
|
4254
4701
|
key,
|
|
@@ -4269,6 +4716,7 @@ var telemetry = {
|
|
|
4269
4716
|
runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
|
|
4270
4717
|
};
|
|
4271
4718
|
var serve = (routeFunction, options) => {
|
|
4719
|
+
void (0, import_qstash13.startDevServer)();
|
|
4272
4720
|
const handler = defineEventHandler(async (event) => {
|
|
4273
4721
|
const method = event.node.req.method;
|
|
4274
4722
|
if (method?.toUpperCase() !== "POST") {
|