@upstash/workflow 0.1.4 → 0.2.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 +1 -1
- package/astro.d.ts +1 -1
- package/astro.js +348 -191
- package/astro.mjs +1 -1
- package/{chunk-HO2SB246.mjs → chunk-5R2BFC3N.mjs} +419 -194
- package/cloudflare.d.mts +1 -1
- package/cloudflare.d.ts +1 -1
- package/cloudflare.js +348 -191
- package/cloudflare.mjs +1 -1
- package/express.d.mts +1 -1
- package/express.d.ts +1 -1
- package/express.js +369 -189
- package/express.mjs +17 -4
- package/h3.d.mts +1 -1
- package/h3.d.ts +1 -1
- package/h3.js +348 -191
- package/h3.mjs +1 -1
- package/hono.d.mts +1 -1
- package/hono.d.ts +1 -1
- package/hono.js +348 -191
- package/hono.mjs +1 -1
- package/index.d.mts +74 -21
- package/index.d.ts +74 -21
- package/index.js +426 -211
- package/index.mjs +5 -5
- package/nextjs.d.mts +1 -1
- package/nextjs.d.ts +1 -1
- package/nextjs.js +348 -191
- package/nextjs.mjs +1 -1
- package/package.json +1 -1
- package/solidjs.d.mts +1 -1
- package/solidjs.d.ts +1 -1
- package/solidjs.js +348 -191
- package/solidjs.mjs +1 -1
- package/svelte.d.mts +1 -1
- package/svelte.d.ts +1 -1
- package/svelte.js +348 -191
- package/svelte.mjs +1 -1
- package/{types-CQuc-j8n.d.mts → types-Cki_MHrh.d.mts} +85 -31
- package/{types-CQuc-j8n.d.ts → types-Cki_MHrh.d.ts} +85 -31
package/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,24 +15,16 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/index.ts
|
|
31
21
|
var src_exports = {};
|
|
32
22
|
__export(src_exports, {
|
|
33
23
|
Client: () => Client3,
|
|
34
|
-
QStashWorkflowAbort: () => QStashWorkflowAbort,
|
|
35
|
-
QStashWorkflowError: () => QStashWorkflowError,
|
|
36
24
|
StepTypes: () => StepTypes,
|
|
25
|
+
WorkflowAbort: () => WorkflowAbort,
|
|
37
26
|
WorkflowContext: () => WorkflowContext,
|
|
27
|
+
WorkflowError: () => WorkflowError,
|
|
38
28
|
WorkflowLogger: () => WorkflowLogger,
|
|
39
29
|
serve: () => serve
|
|
40
30
|
});
|
|
@@ -42,22 +32,33 @@ module.exports = __toCommonJS(src_exports);
|
|
|
42
32
|
|
|
43
33
|
// src/error.ts
|
|
44
34
|
var import_qstash = require("@upstash/qstash");
|
|
45
|
-
var
|
|
35
|
+
var WorkflowError = class extends import_qstash.QstashError {
|
|
46
36
|
constructor(message) {
|
|
47
37
|
super(message);
|
|
48
|
-
this.name = "
|
|
38
|
+
this.name = "WorkflowError";
|
|
49
39
|
}
|
|
50
40
|
};
|
|
51
|
-
var
|
|
41
|
+
var WorkflowAbort = class extends Error {
|
|
52
42
|
stepInfo;
|
|
53
43
|
stepName;
|
|
54
|
-
|
|
44
|
+
/**
|
|
45
|
+
* whether workflow is to be canceled on abort
|
|
46
|
+
*/
|
|
47
|
+
cancelWorkflow;
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
* @param stepName name of the aborting step
|
|
51
|
+
* @param stepInfo step information
|
|
52
|
+
* @param cancelWorkflow
|
|
53
|
+
*/
|
|
54
|
+
constructor(stepName, stepInfo, cancelWorkflow = false) {
|
|
55
55
|
super(
|
|
56
56
|
`This is an Upstash Workflow error thrown after a step executes. It is expected to be raised. Make sure that you await for each step. Also, if you are using try/catch blocks, you should not wrap context.run/sleep/sleepUntil/call methods with try/catch. Aborting workflow after executing step '${stepName}'.`
|
|
57
57
|
);
|
|
58
|
-
this.name = "
|
|
58
|
+
this.name = "WorkflowAbort";
|
|
59
59
|
this.stepName = stepName;
|
|
60
60
|
this.stepInfo = stepInfo;
|
|
61
|
+
this.cancelWorkflow = cancelWorkflow;
|
|
61
62
|
}
|
|
62
63
|
};
|
|
63
64
|
var formatWorkflowError = (error) => {
|
|
@@ -86,6 +87,44 @@ var makeGetWaitersRequest = async (requester, eventId) => {
|
|
|
86
87
|
});
|
|
87
88
|
return result;
|
|
88
89
|
};
|
|
90
|
+
var makeCancelRequest = async (requester, workflowRunId) => {
|
|
91
|
+
await requester.request({
|
|
92
|
+
path: ["v2", "workflows", "runs", `${workflowRunId}?cancel=true`],
|
|
93
|
+
method: "DELETE",
|
|
94
|
+
parseResponseAsJson: false
|
|
95
|
+
});
|
|
96
|
+
return true;
|
|
97
|
+
};
|
|
98
|
+
var getSteps = async (requester, workflowRunId, messageId, debug) => {
|
|
99
|
+
try {
|
|
100
|
+
const steps = await requester.request({
|
|
101
|
+
path: ["v2", "workflows", "runs", workflowRunId],
|
|
102
|
+
parseResponseAsJson: true
|
|
103
|
+
});
|
|
104
|
+
if (!messageId) {
|
|
105
|
+
await debug?.log("INFO", "ENDPOINT_START", {
|
|
106
|
+
message: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
|
|
107
|
+
});
|
|
108
|
+
return steps;
|
|
109
|
+
} else {
|
|
110
|
+
const index = steps.findIndex((item) => item.messageId === messageId);
|
|
111
|
+
if (index === -1) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const filteredSteps = steps.slice(0, index + 1);
|
|
115
|
+
await debug?.log("INFO", "ENDPOINT_START", {
|
|
116
|
+
message: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
|
|
117
|
+
});
|
|
118
|
+
return filteredSteps;
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
await debug?.log("ERROR", "ERROR", {
|
|
122
|
+
message: "failed while fetching steps.",
|
|
123
|
+
error
|
|
124
|
+
});
|
|
125
|
+
throw new WorkflowError(`Failed while pulling steps. ${error}`);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
89
128
|
|
|
90
129
|
// src/context/steps.ts
|
|
91
130
|
var BaseLazyStep = class {
|
|
@@ -700,6 +739,7 @@ var StepTypes = [
|
|
|
700
739
|
];
|
|
701
740
|
|
|
702
741
|
// src/workflow-requests.ts
|
|
742
|
+
var import_qstash2 = require("@upstash/qstash");
|
|
703
743
|
var triggerFirstInvocation = async (workflowContext, retries, debug) => {
|
|
704
744
|
const { headers } = getHeaders(
|
|
705
745
|
"true",
|
|
@@ -710,20 +750,32 @@ var triggerFirstInvocation = async (workflowContext, retries, debug) => {
|
|
|
710
750
|
workflowContext.failureUrl,
|
|
711
751
|
retries
|
|
712
752
|
);
|
|
713
|
-
await debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
|
|
714
|
-
headers,
|
|
715
|
-
requestPayload: workflowContext.requestPayload,
|
|
716
|
-
url: workflowContext.url
|
|
717
|
-
});
|
|
718
753
|
try {
|
|
719
754
|
const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
|
|
720
|
-
await workflowContext.qstashClient.publish({
|
|
755
|
+
const result = await workflowContext.qstashClient.publish({
|
|
721
756
|
headers,
|
|
722
757
|
method: "POST",
|
|
723
758
|
body,
|
|
724
759
|
url: workflowContext.url
|
|
725
760
|
});
|
|
726
|
-
|
|
761
|
+
if (result.deduplicated) {
|
|
762
|
+
await debug?.log("WARN", "SUBMIT_FIRST_INVOCATION", {
|
|
763
|
+
message: `Workflow run ${workflowContext.workflowRunId} already exists. A new one isn't created.`,
|
|
764
|
+
headers,
|
|
765
|
+
requestPayload: workflowContext.requestPayload,
|
|
766
|
+
url: workflowContext.url,
|
|
767
|
+
messageId: result.messageId
|
|
768
|
+
});
|
|
769
|
+
return ok("workflow-run-already-exists");
|
|
770
|
+
} else {
|
|
771
|
+
await debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
|
|
772
|
+
headers,
|
|
773
|
+
requestPayload: workflowContext.requestPayload,
|
|
774
|
+
url: workflowContext.url,
|
|
775
|
+
messageId: result.messageId
|
|
776
|
+
});
|
|
777
|
+
return ok("success");
|
|
778
|
+
}
|
|
727
779
|
} catch (error) {
|
|
728
780
|
const error_ = error;
|
|
729
781
|
return err(error_);
|
|
@@ -731,7 +783,9 @@ var triggerFirstInvocation = async (workflowContext, retries, debug) => {
|
|
|
731
783
|
};
|
|
732
784
|
var triggerRouteFunction = async ({
|
|
733
785
|
onCleanup,
|
|
734
|
-
onStep
|
|
786
|
+
onStep,
|
|
787
|
+
onCancel,
|
|
788
|
+
debug
|
|
735
789
|
}) => {
|
|
736
790
|
try {
|
|
737
791
|
await onStep();
|
|
@@ -739,19 +793,50 @@ var triggerRouteFunction = async ({
|
|
|
739
793
|
return ok("workflow-finished");
|
|
740
794
|
} catch (error) {
|
|
741
795
|
const error_ = error;
|
|
742
|
-
|
|
796
|
+
if (error instanceof import_qstash2.QstashError && error.status === 400) {
|
|
797
|
+
await debug?.log("WARN", "RESPONSE_WORKFLOW", {
|
|
798
|
+
message: `tried to append to a cancelled workflow. exiting without publishing.`,
|
|
799
|
+
name: error.name,
|
|
800
|
+
errorMessage: error.message
|
|
801
|
+
});
|
|
802
|
+
return ok("workflow-was-finished");
|
|
803
|
+
} else if (!(error_ instanceof WorkflowAbort)) {
|
|
804
|
+
return err(error_);
|
|
805
|
+
} else if (error_.cancelWorkflow) {
|
|
806
|
+
await onCancel();
|
|
807
|
+
return ok("workflow-finished");
|
|
808
|
+
} else {
|
|
809
|
+
return ok("step-finished");
|
|
810
|
+
}
|
|
743
811
|
}
|
|
744
812
|
};
|
|
745
813
|
var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
|
|
746
814
|
await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
|
|
747
815
|
deletedWorkflowRunId: workflowContext.workflowRunId
|
|
748
816
|
});
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
817
|
+
try {
|
|
818
|
+
await workflowContext.qstashClient.http.request({
|
|
819
|
+
path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
|
|
820
|
+
method: "DELETE",
|
|
821
|
+
parseResponseAsJson: false
|
|
822
|
+
});
|
|
823
|
+
await debug?.log(
|
|
824
|
+
"SUBMIT",
|
|
825
|
+
"SUBMIT_CLEANUP",
|
|
826
|
+
`workflow run ${workflowContext.workflowRunId} deleted.`
|
|
827
|
+
);
|
|
828
|
+
return { deleted: true };
|
|
829
|
+
} catch (error) {
|
|
830
|
+
if (error instanceof import_qstash2.QstashError && error.status === 404) {
|
|
831
|
+
await debug?.log("WARN", "SUBMIT_CLEANUP", {
|
|
832
|
+
message: `Failed to remove workflow run ${workflowContext.workflowRunId} as it doesn't exist.`,
|
|
833
|
+
name: error.name,
|
|
834
|
+
errorMessage: error.message
|
|
835
|
+
});
|
|
836
|
+
return { deleted: false };
|
|
837
|
+
}
|
|
838
|
+
throw error;
|
|
839
|
+
}
|
|
755
840
|
};
|
|
756
841
|
var recreateUserHeaders = (headers) => {
|
|
757
842
|
const filteredHeaders = new Headers();
|
|
@@ -767,15 +852,32 @@ var recreateUserHeaders = (headers) => {
|
|
|
767
852
|
var handleThirdPartyCallResult = async (request, requestPayload, client, workflowUrl, failureUrl, retries, debug) => {
|
|
768
853
|
try {
|
|
769
854
|
if (request.headers.get("Upstash-Workflow-Callback")) {
|
|
770
|
-
|
|
855
|
+
let callbackPayload;
|
|
856
|
+
if (requestPayload) {
|
|
857
|
+
callbackPayload = requestPayload;
|
|
858
|
+
} else {
|
|
859
|
+
const workflowRunId2 = request.headers.get("upstash-workflow-runid");
|
|
860
|
+
const messageId = request.headers.get("upstash-message-id");
|
|
861
|
+
if (!workflowRunId2)
|
|
862
|
+
throw new WorkflowError("workflow run id missing in context.call lazy fetch.");
|
|
863
|
+
if (!messageId) throw new WorkflowError("message id missing in context.call lazy fetch.");
|
|
864
|
+
const steps = await getSteps(client.http, workflowRunId2, messageId, debug);
|
|
865
|
+
const failingStep = steps.find((step) => step.messageId === messageId);
|
|
866
|
+
if (!failingStep)
|
|
867
|
+
throw new WorkflowError(
|
|
868
|
+
"Failed to submit the context.call." + (steps.length === 0 ? "No steps found." : `No step was found with matching messageId ${messageId} out of ${steps.length} steps.`)
|
|
869
|
+
);
|
|
870
|
+
callbackPayload = atob(failingStep.body);
|
|
871
|
+
}
|
|
872
|
+
const callbackMessage = JSON.parse(callbackPayload);
|
|
771
873
|
if (!(callbackMessage.status >= 200 && callbackMessage.status < 300) && callbackMessage.maxRetries && callbackMessage.retried !== callbackMessage.maxRetries) {
|
|
772
874
|
await debug?.log("WARN", "SUBMIT_THIRD_PARTY_RESULT", {
|
|
773
875
|
status: callbackMessage.status,
|
|
774
|
-
body: atob(callbackMessage.body)
|
|
876
|
+
body: atob(callbackMessage.body ?? "")
|
|
775
877
|
});
|
|
776
878
|
console.warn(
|
|
777
879
|
`Workflow Warning: "context.call" failed with status ${callbackMessage.status} and will retry (retried ${callbackMessage.retried ?? 0} out of ${callbackMessage.maxRetries} times). Error Message:
|
|
778
|
-
${atob(callbackMessage.body)}`
|
|
880
|
+
${atob(callbackMessage.body ?? "")}`
|
|
779
881
|
);
|
|
780
882
|
return ok("call-will-retry");
|
|
781
883
|
}
|
|
@@ -809,7 +911,7 @@ ${atob(callbackMessage.body)}`
|
|
|
809
911
|
);
|
|
810
912
|
const callResponse = {
|
|
811
913
|
status: callbackMessage.status,
|
|
812
|
-
body: atob(callbackMessage.body),
|
|
914
|
+
body: atob(callbackMessage.body ?? ""),
|
|
813
915
|
header: callbackMessage.header
|
|
814
916
|
};
|
|
815
917
|
const callResultStep = {
|
|
@@ -840,9 +942,7 @@ ${atob(callbackMessage.body)}`
|
|
|
840
942
|
} catch (error) {
|
|
841
943
|
const isCallReturn = request.headers.get("Upstash-Workflow-Callback");
|
|
842
944
|
return err(
|
|
843
|
-
new
|
|
844
|
-
`Error when handling call return (isCallReturn=${isCallReturn}): ${error}`
|
|
845
|
-
)
|
|
945
|
+
new WorkflowError(`Error when handling call return (isCallReturn=${isCallReturn}): ${error}`)
|
|
846
946
|
);
|
|
847
947
|
}
|
|
848
948
|
};
|
|
@@ -850,7 +950,8 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
|
|
|
850
950
|
const baseHeaders = {
|
|
851
951
|
[WORKFLOW_INIT_HEADER]: initHeaderValue,
|
|
852
952
|
[WORKFLOW_ID_HEADER]: workflowRunId,
|
|
853
|
-
[WORKFLOW_URL_HEADER]: workflowUrl
|
|
953
|
+
[WORKFLOW_URL_HEADER]: workflowUrl,
|
|
954
|
+
[WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody"
|
|
854
955
|
};
|
|
855
956
|
if (!step?.callUrl) {
|
|
856
957
|
baseHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
|
|
@@ -863,8 +964,8 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
|
|
|
863
964
|
}
|
|
864
965
|
if (step?.callUrl) {
|
|
865
966
|
baseHeaders["Upstash-Retries"] = callRetries?.toString() ?? "0";
|
|
866
|
-
baseHeaders[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete";
|
|
867
|
-
if (retries) {
|
|
967
|
+
baseHeaders[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete,InitialBody";
|
|
968
|
+
if (retries !== void 0) {
|
|
868
969
|
baseHeaders["Upstash-Callback-Retries"] = retries.toString();
|
|
869
970
|
baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
|
|
870
971
|
}
|
|
@@ -899,6 +1000,7 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
|
|
|
899
1000
|
"Upstash-Callback-Workflow-CallType": "fromCallback",
|
|
900
1001
|
"Upstash-Callback-Workflow-Init": "false",
|
|
901
1002
|
"Upstash-Callback-Workflow-Url": workflowUrl,
|
|
1003
|
+
"Upstash-Callback-Feature-Set": "LazyFetch,InitialBody",
|
|
902
1004
|
"Upstash-Callback-Forward-Upstash-Workflow-Callback": "true",
|
|
903
1005
|
"Upstash-Callback-Forward-Upstash-Workflow-StepId": step.stepId.toString(),
|
|
904
1006
|
"Upstash-Callback-Forward-Upstash-Workflow-StepName": step.stepName,
|
|
@@ -947,7 +1049,7 @@ var verifyRequest = async (body, signature, verifier) => {
|
|
|
947
1049
|
throw new Error("Signature in `Upstash-Signature` header is not valid");
|
|
948
1050
|
}
|
|
949
1051
|
} catch (error) {
|
|
950
|
-
throw new
|
|
1052
|
+
throw new WorkflowError(
|
|
951
1053
|
`Failed to verify that the Workflow request comes from QStash: ${error}
|
|
952
1054
|
|
|
953
1055
|
If signature is missing, trigger the workflow endpoint by publishing your request to QStash instead of calling it directly.
|
|
@@ -987,14 +1089,14 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
987
1089
|
*
|
|
988
1090
|
* If a function is already executing (this.executingStep), this
|
|
989
1091
|
* means that there is a nested step which is not allowed. In this
|
|
990
|
-
* case, addStep throws
|
|
1092
|
+
* case, addStep throws WorkflowError.
|
|
991
1093
|
*
|
|
992
1094
|
* @param stepInfo step plan to add
|
|
993
1095
|
* @returns result of the step function
|
|
994
1096
|
*/
|
|
995
1097
|
async addStep(stepInfo) {
|
|
996
1098
|
if (this.executingStep) {
|
|
997
|
-
throw new
|
|
1099
|
+
throw new WorkflowError(
|
|
998
1100
|
`A step can not be run inside another step. Tried to run '${stepInfo.stepName}' inside '${this.executingStep}'`
|
|
999
1101
|
);
|
|
1000
1102
|
}
|
|
@@ -1079,7 +1181,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1079
1181
|
const sortedSteps = sortSteps(this.steps);
|
|
1080
1182
|
const plannedParallelStepCount = sortedSteps[initialStepCount + this.planStepCount]?.concurrent;
|
|
1081
1183
|
if (parallelCallState !== "first" && plannedParallelStepCount !== parallelSteps.length) {
|
|
1082
|
-
throw new
|
|
1184
|
+
throw new WorkflowError(
|
|
1083
1185
|
`Incompatible number of parallel steps when call state was '${parallelCallState}'. Expected ${parallelSteps.length}, got ${plannedParallelStepCount} from the request.`
|
|
1084
1186
|
);
|
|
1085
1187
|
}
|
|
@@ -1101,7 +1203,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1101
1203
|
case "partial": {
|
|
1102
1204
|
const planStep = this.steps.at(-1);
|
|
1103
1205
|
if (!planStep || planStep.targetStep === void 0) {
|
|
1104
|
-
throw new
|
|
1206
|
+
throw new WorkflowError(
|
|
1105
1207
|
`There must be a last step and it should have targetStep larger than 0.Received: ${JSON.stringify(planStep)}`
|
|
1106
1208
|
);
|
|
1107
1209
|
}
|
|
@@ -1115,17 +1217,17 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1115
1217
|
);
|
|
1116
1218
|
await this.submitStepsToQStash([resultStep], [parallelStep]);
|
|
1117
1219
|
} catch (error) {
|
|
1118
|
-
if (error instanceof
|
|
1220
|
+
if (error instanceof WorkflowAbort) {
|
|
1119
1221
|
throw error;
|
|
1120
1222
|
}
|
|
1121
|
-
throw new
|
|
1223
|
+
throw new WorkflowError(
|
|
1122
1224
|
`Error submitting steps to QStash in partial parallel step execution: ${error}`
|
|
1123
1225
|
);
|
|
1124
1226
|
}
|
|
1125
1227
|
break;
|
|
1126
1228
|
}
|
|
1127
1229
|
case "discard": {
|
|
1128
|
-
throw new
|
|
1230
|
+
throw new WorkflowAbort("discarded parallel");
|
|
1129
1231
|
}
|
|
1130
1232
|
case "last": {
|
|
1131
1233
|
const parallelResultSteps = sortedSteps.filter((step) => step.stepId >= initialStepCount).slice(0, parallelSteps.length);
|
|
@@ -1176,7 +1278,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1176
1278
|
*/
|
|
1177
1279
|
async submitStepsToQStash(steps, lazySteps) {
|
|
1178
1280
|
if (steps.length === 0) {
|
|
1179
|
-
throw new
|
|
1281
|
+
throw new WorkflowError(
|
|
1180
1282
|
`Unable to submit steps to QStash. Provided list is empty. Current step: ${this.stepCount}`
|
|
1181
1283
|
);
|
|
1182
1284
|
}
|
|
@@ -1216,7 +1318,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1216
1318
|
method: "POST",
|
|
1217
1319
|
parseResponseAsJson: false
|
|
1218
1320
|
});
|
|
1219
|
-
throw new
|
|
1321
|
+
throw new WorkflowAbort(steps[0].stepName, steps[0]);
|
|
1220
1322
|
}
|
|
1221
1323
|
const result = await this.context.qstashClient.batchJSON(
|
|
1222
1324
|
steps.map((singleStep, index) => {
|
|
@@ -1268,7 +1370,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1268
1370
|
};
|
|
1269
1371
|
})
|
|
1270
1372
|
});
|
|
1271
|
-
throw new
|
|
1373
|
+
throw new WorkflowAbort(steps[0].stepName, steps[0]);
|
|
1272
1374
|
}
|
|
1273
1375
|
/**
|
|
1274
1376
|
* Get the promise by executing the lazt steps list. If there is a single
|
|
@@ -1293,7 +1395,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1293
1395
|
} else if (Array.isArray(result) && lazyStepList.length === result.length && index < lazyStepList.length) {
|
|
1294
1396
|
return result[index];
|
|
1295
1397
|
} else {
|
|
1296
|
-
throw new
|
|
1398
|
+
throw new WorkflowError(
|
|
1297
1399
|
`Unexpected parallel call result while executing step ${index}: '${result}'. Expected ${lazyStepList.length} many items`
|
|
1298
1400
|
);
|
|
1299
1401
|
}
|
|
@@ -1305,12 +1407,12 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1305
1407
|
};
|
|
1306
1408
|
var validateStep = (lazyStep, stepFromRequest) => {
|
|
1307
1409
|
if (lazyStep.stepName !== stepFromRequest.stepName) {
|
|
1308
|
-
throw new
|
|
1410
|
+
throw new WorkflowError(
|
|
1309
1411
|
`Incompatible step name. Expected '${lazyStep.stepName}', got '${stepFromRequest.stepName}' from the request`
|
|
1310
1412
|
);
|
|
1311
1413
|
}
|
|
1312
1414
|
if (lazyStep.stepType !== stepFromRequest.stepType) {
|
|
1313
|
-
throw new
|
|
1415
|
+
throw new WorkflowError(
|
|
1314
1416
|
`Incompatible step type. Expected '${lazyStep.stepType}', got '${stepFromRequest.stepType}' from the request`
|
|
1315
1417
|
);
|
|
1316
1418
|
}
|
|
@@ -1321,12 +1423,12 @@ var validateParallelSteps = (lazySteps, stepsFromRequest) => {
|
|
|
1321
1423
|
validateStep(lazySteps[index], stepFromRequest);
|
|
1322
1424
|
}
|
|
1323
1425
|
} catch (error) {
|
|
1324
|
-
if (error instanceof
|
|
1426
|
+
if (error instanceof WorkflowError) {
|
|
1325
1427
|
const lazyStepNames = lazySteps.map((lazyStep) => lazyStep.stepName);
|
|
1326
1428
|
const lazyStepTypes = lazySteps.map((lazyStep) => lazyStep.stepType);
|
|
1327
1429
|
const requestStepNames = stepsFromRequest.map((step) => step.stepName);
|
|
1328
1430
|
const requestStepTypes = stepsFromRequest.map((step) => step.stepType);
|
|
1329
|
-
throw new
|
|
1431
|
+
throw new WorkflowError(
|
|
1330
1432
|
`Incompatible steps detected in parallel execution: ${error.message}
|
|
1331
1433
|
> Step Names from the request: ${JSON.stringify(requestStepNames)}
|
|
1332
1434
|
Step Types from the request: ${JSON.stringify(requestStepTypes)}
|
|
@@ -1439,10 +1541,6 @@ var WorkflowContext = class {
|
|
|
1439
1541
|
* headers of the initial request
|
|
1440
1542
|
*/
|
|
1441
1543
|
headers;
|
|
1442
|
-
/**
|
|
1443
|
-
* initial payload as a raw string
|
|
1444
|
-
*/
|
|
1445
|
-
rawInitialPayload;
|
|
1446
1544
|
/**
|
|
1447
1545
|
* Map of environment variables and their values.
|
|
1448
1546
|
*
|
|
@@ -1477,7 +1575,6 @@ var WorkflowContext = class {
|
|
|
1477
1575
|
failureUrl,
|
|
1478
1576
|
debug,
|
|
1479
1577
|
initialPayload,
|
|
1480
|
-
rawInitialPayload,
|
|
1481
1578
|
env,
|
|
1482
1579
|
retries
|
|
1483
1580
|
}) {
|
|
@@ -1488,7 +1585,6 @@ var WorkflowContext = class {
|
|
|
1488
1585
|
this.failureUrl = failureUrl;
|
|
1489
1586
|
this.headers = headers;
|
|
1490
1587
|
this.requestPayload = initialPayload;
|
|
1491
|
-
this.rawInitialPayload = rawInitialPayload ?? JSON.stringify(this.requestPayload);
|
|
1492
1588
|
this.env = env ?? {};
|
|
1493
1589
|
this.retries = retries ?? DEFAULT_RETRIES;
|
|
1494
1590
|
this.executor = new AutoExecutor(this, this.steps, debug);
|
|
@@ -1509,7 +1605,7 @@ var WorkflowContext = class {
|
|
|
1509
1605
|
* const [result1, result2] = await Promise.all([
|
|
1510
1606
|
* context.run("step 1", () => {
|
|
1511
1607
|
* return "result1"
|
|
1512
|
-
* })
|
|
1608
|
+
* }),
|
|
1513
1609
|
* context.run("step 2", async () => {
|
|
1514
1610
|
* return await fetchResults()
|
|
1515
1611
|
* })
|
|
@@ -1527,6 +1623,10 @@ var WorkflowContext = class {
|
|
|
1527
1623
|
/**
|
|
1528
1624
|
* Stops the execution for the duration provided.
|
|
1529
1625
|
*
|
|
1626
|
+
* ```typescript
|
|
1627
|
+
* await context.sleep('sleep1', 3) // wait for three seconds
|
|
1628
|
+
* ```
|
|
1629
|
+
*
|
|
1530
1630
|
* @param stepName
|
|
1531
1631
|
* @param duration sleep duration in seconds
|
|
1532
1632
|
* @returns undefined
|
|
@@ -1537,6 +1637,10 @@ var WorkflowContext = class {
|
|
|
1537
1637
|
/**
|
|
1538
1638
|
* Stops the execution until the date time provided.
|
|
1539
1639
|
*
|
|
1640
|
+
* ```typescript
|
|
1641
|
+
* await context.sleepUntil('sleep1', Date.now() / 1000 + 3) // wait for three seconds
|
|
1642
|
+
* ```
|
|
1643
|
+
*
|
|
1540
1644
|
* @param stepName
|
|
1541
1645
|
* @param datetime time to sleep until. Can be provided as a number (in unix seconds),
|
|
1542
1646
|
* as a Date object or a string (passed to `new Date(datetimeString)`)
|
|
@@ -1560,7 +1664,7 @@ var WorkflowContext = class {
|
|
|
1560
1664
|
* const { status, body } = await context.call<string>(
|
|
1561
1665
|
* "post call step",
|
|
1562
1666
|
* {
|
|
1563
|
-
* url:
|
|
1667
|
+
* url: "https://www.some-endpoint.com/api",
|
|
1564
1668
|
* method: "POST",
|
|
1565
1669
|
* body: "my-payload"
|
|
1566
1670
|
* }
|
|
@@ -1614,45 +1718,43 @@ var WorkflowContext = class {
|
|
|
1614
1718
|
}
|
|
1615
1719
|
}
|
|
1616
1720
|
/**
|
|
1617
|
-
*
|
|
1618
|
-
* timeout ends
|
|
1721
|
+
* Pauses workflow execution until a specific event occurs or a timeout is reached.
|
|
1619
1722
|
*
|
|
1620
|
-
|
|
1621
|
-
* const
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1624
|
-
|
|
1625
|
-
* );
|
|
1626
|
-
* ```
|
|
1723
|
+
*```ts
|
|
1724
|
+
* const result = await workflow.waitForEvent("payment-confirmed", {
|
|
1725
|
+
* timeout: "5m"
|
|
1726
|
+
* });
|
|
1727
|
+
*```
|
|
1627
1728
|
*
|
|
1628
|
-
* To notify a waiting workflow
|
|
1729
|
+
* To notify a waiting workflow:
|
|
1629
1730
|
*
|
|
1630
1731
|
* ```ts
|
|
1631
1732
|
* import { Client } from "@upstash/workflow";
|
|
1632
1733
|
*
|
|
1633
|
-
* const client = new Client({ token: });
|
|
1734
|
+
* const client = new Client({ token: "<QSTASH_TOKEN>" });
|
|
1634
1735
|
*
|
|
1635
1736
|
* await client.notify({
|
|
1636
|
-
* eventId: "
|
|
1637
|
-
*
|
|
1737
|
+
* eventId: "payment.confirmed",
|
|
1738
|
+
* data: {
|
|
1739
|
+
* amount: 99.99,
|
|
1740
|
+
* currency: "USD"
|
|
1741
|
+
* }
|
|
1638
1742
|
* })
|
|
1639
1743
|
* ```
|
|
1640
1744
|
*
|
|
1745
|
+
* Alternatively, you can use the `context.notify` method.
|
|
1746
|
+
*
|
|
1641
1747
|
* @param stepName
|
|
1642
|
-
* @param eventId
|
|
1643
|
-
* @param
|
|
1644
|
-
* @returns
|
|
1645
|
-
* timeout
|
|
1646
|
-
* is the
|
|
1748
|
+
* @param eventId - Unique identifier for the event to wait for
|
|
1749
|
+
* @param options - Configuration options.
|
|
1750
|
+
* @returns `{ timeout: boolean, eventData: unknown }`.
|
|
1751
|
+
* The `timeout` property specifies if the workflow has timed out. The `eventData`
|
|
1752
|
+
* is the data passed when notifying this workflow of an event.
|
|
1647
1753
|
*/
|
|
1648
|
-
async waitForEvent(stepName, eventId,
|
|
1649
|
-
const
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
eventId,
|
|
1653
|
-
typeof timeout === "string" ? timeout : `${timeout}s`
|
|
1654
|
-
)
|
|
1655
|
-
);
|
|
1754
|
+
async waitForEvent(stepName, eventId, options = {}) {
|
|
1755
|
+
const { timeout = "7d" } = options;
|
|
1756
|
+
const timeoutStr = typeof timeout === "string" ? timeout : `${timeout}s`;
|
|
1757
|
+
const result = await this.addStep(new LazyWaitForEventStep(stepName, eventId, timeoutStr));
|
|
1656
1758
|
try {
|
|
1657
1759
|
return {
|
|
1658
1760
|
...result,
|
|
@@ -1662,6 +1764,27 @@ var WorkflowContext = class {
|
|
|
1662
1764
|
return result;
|
|
1663
1765
|
}
|
|
1664
1766
|
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Notify workflow runs waiting for an event
|
|
1769
|
+
*
|
|
1770
|
+
* ```ts
|
|
1771
|
+
* const { eventId, eventData, notifyResponse } = await context.notify(
|
|
1772
|
+
* "notify step", "event-id", "event-data"
|
|
1773
|
+
* );
|
|
1774
|
+
* ```
|
|
1775
|
+
*
|
|
1776
|
+
* Upon `context.notify`, the workflow runs waiting for the given eventId (context.waitForEvent)
|
|
1777
|
+
* will receive the given event data and resume execution.
|
|
1778
|
+
*
|
|
1779
|
+
* The response includes the same eventId and eventData. Additionally, there is
|
|
1780
|
+
* a notifyResponse field which contains a list of `Waiter` objects, each corresponding
|
|
1781
|
+
* to a notified workflow run.
|
|
1782
|
+
*
|
|
1783
|
+
* @param stepName
|
|
1784
|
+
* @param eventId event id to notify
|
|
1785
|
+
* @param eventData event data to notify with
|
|
1786
|
+
* @returns notify response which has event id, event data and list of waiters which were notified
|
|
1787
|
+
*/
|
|
1665
1788
|
async notify(stepName, eventId, eventData) {
|
|
1666
1789
|
const result = await this.addStep(
|
|
1667
1790
|
new LazyNotifyStep(stepName, eventId, eventData, this.qstashClient.http)
|
|
@@ -1675,6 +1798,15 @@ var WorkflowContext = class {
|
|
|
1675
1798
|
return result;
|
|
1676
1799
|
}
|
|
1677
1800
|
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Cancel the current workflow run
|
|
1803
|
+
*
|
|
1804
|
+
* Will throw WorkflowAbort to stop workflow execution.
|
|
1805
|
+
* Shouldn't be inside try/catch.
|
|
1806
|
+
*/
|
|
1807
|
+
async cancel() {
|
|
1808
|
+
throw new WorkflowAbort("cancel", void 0, true);
|
|
1809
|
+
}
|
|
1678
1810
|
/**
|
|
1679
1811
|
* Adds steps to the executor. Needed so that it can be overwritten in
|
|
1680
1812
|
* DisabledWorkflowContext.
|
|
@@ -1715,7 +1847,8 @@ var WorkflowLogger = class _WorkflowLogger {
|
|
|
1715
1847
|
}
|
|
1716
1848
|
writeToConsole(logEntry) {
|
|
1717
1849
|
const JSON_SPACING = 2;
|
|
1718
|
-
console.
|
|
1850
|
+
const logMethod = logEntry.logLevel === "ERROR" ? console.error : logEntry.logLevel === "WARN" ? console.warn : console.log;
|
|
1851
|
+
logMethod(JSON.stringify(logEntry, void 0, JSON_SPACING));
|
|
1719
1852
|
}
|
|
1720
1853
|
shouldLog(level) {
|
|
1721
1854
|
return LOG_LEVELS.indexOf(level) >= LOG_LEVELS.indexOf(this.options.logLevel);
|
|
@@ -1733,11 +1866,13 @@ var WorkflowLogger = class _WorkflowLogger {
|
|
|
1733
1866
|
};
|
|
1734
1867
|
|
|
1735
1868
|
// src/utils.ts
|
|
1736
|
-
var import_node_crypto = __toESM(require("crypto"));
|
|
1737
1869
|
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
1738
1870
|
var NANOID_LENGTH = 21;
|
|
1871
|
+
function getRandomInt() {
|
|
1872
|
+
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
1873
|
+
}
|
|
1739
1874
|
function nanoid() {
|
|
1740
|
-
return
|
|
1875
|
+
return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
1741
1876
|
}
|
|
1742
1877
|
function getWorkflowRunId(id) {
|
|
1743
1878
|
return `wfr_${id ?? nanoid()}`;
|
|
@@ -1755,6 +1890,63 @@ function decodeBase64(base64) {
|
|
|
1755
1890
|
}
|
|
1756
1891
|
}
|
|
1757
1892
|
|
|
1893
|
+
// src/serve/authorization.ts
|
|
1894
|
+
var import_qstash3 = require("@upstash/qstash");
|
|
1895
|
+
var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
|
|
1896
|
+
static disabledMessage = "disabled-qstash-worklfow-run";
|
|
1897
|
+
/**
|
|
1898
|
+
* overwrite the WorkflowContext.addStep method to always raise WorkflowAbort
|
|
1899
|
+
* error in order to stop the execution whenever we encounter a step.
|
|
1900
|
+
*
|
|
1901
|
+
* @param _step
|
|
1902
|
+
*/
|
|
1903
|
+
async addStep(_step) {
|
|
1904
|
+
throw new WorkflowAbort(_DisabledWorkflowContext.disabledMessage);
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* overwrite cancel method to do nothing
|
|
1908
|
+
*/
|
|
1909
|
+
async cancel() {
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* copies the passed context to create a DisabledWorkflowContext. Then, runs the
|
|
1914
|
+
* route function with the new context.
|
|
1915
|
+
*
|
|
1916
|
+
* - returns "run-ended" if there are no steps found or
|
|
1917
|
+
* if the auth failed and user called `return`
|
|
1918
|
+
* - returns "step-found" if DisabledWorkflowContext.addStep is called.
|
|
1919
|
+
* - if there is another error, returns the error.
|
|
1920
|
+
*
|
|
1921
|
+
* @param routeFunction
|
|
1922
|
+
*/
|
|
1923
|
+
static async tryAuthentication(routeFunction, context) {
|
|
1924
|
+
const disabledContext = new _DisabledWorkflowContext({
|
|
1925
|
+
qstashClient: new import_qstash3.Client({
|
|
1926
|
+
baseUrl: "disabled-client",
|
|
1927
|
+
token: "disabled-client"
|
|
1928
|
+
}),
|
|
1929
|
+
workflowRunId: context.workflowRunId,
|
|
1930
|
+
headers: context.headers,
|
|
1931
|
+
steps: [],
|
|
1932
|
+
url: context.url,
|
|
1933
|
+
failureUrl: context.failureUrl,
|
|
1934
|
+
initialPayload: context.requestPayload,
|
|
1935
|
+
env: context.env,
|
|
1936
|
+
retries: context.retries
|
|
1937
|
+
});
|
|
1938
|
+
try {
|
|
1939
|
+
await routeFunction(disabledContext);
|
|
1940
|
+
} catch (error) {
|
|
1941
|
+
if (error instanceof WorkflowAbort && error.stepName === this.disabledMessage) {
|
|
1942
|
+
return ok("step-found");
|
|
1943
|
+
}
|
|
1944
|
+
return err(error);
|
|
1945
|
+
}
|
|
1946
|
+
return ok("run-ended");
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1758
1950
|
// src/workflow-parser.ts
|
|
1759
1951
|
var getPayload = async (request) => {
|
|
1760
1952
|
try {
|
|
@@ -1763,8 +1955,8 @@ var getPayload = async (request) => {
|
|
|
1763
1955
|
return;
|
|
1764
1956
|
}
|
|
1765
1957
|
};
|
|
1766
|
-
var
|
|
1767
|
-
const [encodedInitialPayload, ...encodedSteps] =
|
|
1958
|
+
var processRawSteps = (rawSteps) => {
|
|
1959
|
+
const [encodedInitialPayload, ...encodedSteps] = rawSteps;
|
|
1768
1960
|
const rawInitialPayload = decodeBase64(encodedInitialPayload.body);
|
|
1769
1961
|
const initialStep = {
|
|
1770
1962
|
stepId: 0,
|
|
@@ -1774,27 +1966,21 @@ var parsePayload = async (rawPayload, debug) => {
|
|
|
1774
1966
|
concurrent: NO_CONCURRENCY
|
|
1775
1967
|
};
|
|
1776
1968
|
const stepsToDecode = encodedSteps.filter((step) => step.callType === "step");
|
|
1777
|
-
const otherSteps =
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
};
|
|
1793
|
-
step.out = newOut;
|
|
1794
|
-
}
|
|
1795
|
-
return step;
|
|
1796
|
-
})
|
|
1797
|
-
);
|
|
1969
|
+
const otherSteps = stepsToDecode.map((rawStep) => {
|
|
1970
|
+
const step = JSON.parse(decodeBase64(rawStep.body));
|
|
1971
|
+
try {
|
|
1972
|
+
step.out = JSON.parse(step.out);
|
|
1973
|
+
} catch {
|
|
1974
|
+
}
|
|
1975
|
+
if (step.waitEventId) {
|
|
1976
|
+
const newOut = {
|
|
1977
|
+
eventData: step.out ? decodeBase64(step.out) : void 0,
|
|
1978
|
+
timeout: step.waitTimeout ?? false
|
|
1979
|
+
};
|
|
1980
|
+
step.out = newOut;
|
|
1981
|
+
}
|
|
1982
|
+
return step;
|
|
1983
|
+
});
|
|
1798
1984
|
const steps = [initialStep, ...otherSteps];
|
|
1799
1985
|
return {
|
|
1800
1986
|
rawInitialPayload,
|
|
@@ -1842,20 +2028,20 @@ var validateRequest = (request) => {
|
|
|
1842
2028
|
const versionHeader = request.headers.get(WORKFLOW_PROTOCOL_VERSION_HEADER);
|
|
1843
2029
|
const isFirstInvocation = !versionHeader;
|
|
1844
2030
|
if (!isFirstInvocation && versionHeader !== WORKFLOW_PROTOCOL_VERSION) {
|
|
1845
|
-
throw new
|
|
2031
|
+
throw new WorkflowError(
|
|
1846
2032
|
`Incompatible workflow sdk protocol version. Expected ${WORKFLOW_PROTOCOL_VERSION}, got ${versionHeader} from the request.`
|
|
1847
2033
|
);
|
|
1848
2034
|
}
|
|
1849
2035
|
const workflowRunId = isFirstInvocation ? getWorkflowRunId() : request.headers.get(WORKFLOW_ID_HEADER) ?? "";
|
|
1850
2036
|
if (workflowRunId.length === 0) {
|
|
1851
|
-
throw new
|
|
2037
|
+
throw new WorkflowError("Couldn't get workflow id from header");
|
|
1852
2038
|
}
|
|
1853
2039
|
return {
|
|
1854
2040
|
isFirstInvocation,
|
|
1855
2041
|
workflowRunId
|
|
1856
2042
|
};
|
|
1857
2043
|
};
|
|
1858
|
-
var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
|
|
2044
|
+
var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requester, messageId, debug) => {
|
|
1859
2045
|
if (isFirstInvocation) {
|
|
1860
2046
|
return {
|
|
1861
2047
|
rawInitialPayload: requestPayload ?? "",
|
|
@@ -1863,10 +2049,18 @@ var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
|
|
|
1863
2049
|
isLastDuplicate: false
|
|
1864
2050
|
};
|
|
1865
2051
|
} else {
|
|
2052
|
+
let rawSteps;
|
|
1866
2053
|
if (!requestPayload) {
|
|
1867
|
-
|
|
2054
|
+
await debug?.log(
|
|
2055
|
+
"INFO",
|
|
2056
|
+
"ENDPOINT_START",
|
|
2057
|
+
"request payload is empty, steps will be fetched from QStash."
|
|
2058
|
+
);
|
|
2059
|
+
rawSteps = await getSteps(requester, workflowRunId, messageId, debug);
|
|
2060
|
+
} else {
|
|
2061
|
+
rawSteps = JSON.parse(requestPayload);
|
|
1868
2062
|
}
|
|
1869
|
-
const { rawInitialPayload, steps } =
|
|
2063
|
+
const { rawInitialPayload, steps } = processRawSteps(rawSteps);
|
|
1870
2064
|
const isLastDuplicate = await checkIfLastOneIsDuplicate(steps, debug);
|
|
1871
2065
|
const deduplicatedSteps = deduplicateSteps(steps);
|
|
1872
2066
|
return {
|
|
@@ -1876,13 +2070,13 @@ var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
|
|
|
1876
2070
|
};
|
|
1877
2071
|
}
|
|
1878
2072
|
};
|
|
1879
|
-
var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, failureFunction, debug) => {
|
|
2073
|
+
var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, debug) => {
|
|
1880
2074
|
if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
|
|
1881
2075
|
return ok("not-failure-callback");
|
|
1882
2076
|
}
|
|
1883
2077
|
if (!failureFunction) {
|
|
1884
2078
|
return err(
|
|
1885
|
-
new
|
|
2079
|
+
new WorkflowError(
|
|
1886
2080
|
"Workflow endpoint is called to handle a failure, but a failureFunction is not provided in serve options. Either provide a failureUrl or a failureFunction."
|
|
1887
2081
|
)
|
|
1888
2082
|
);
|
|
@@ -1893,92 +2087,48 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
|
|
|
1893
2087
|
);
|
|
1894
2088
|
const decodedBody = body ? decodeBase64(body) : "{}";
|
|
1895
2089
|
const errorPayload = JSON.parse(decodedBody);
|
|
1896
|
-
const {
|
|
1897
|
-
rawInitialPayload,
|
|
1898
|
-
steps,
|
|
1899
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1900
|
-
isLastDuplicate: _isLastDuplicate
|
|
1901
|
-
} = await parseRequest(decodeBase64(sourceBody), false, debug);
|
|
1902
2090
|
const workflowContext = new WorkflowContext({
|
|
1903
2091
|
qstashClient,
|
|
1904
2092
|
workflowRunId,
|
|
1905
|
-
initialPayload: initialPayloadParser(
|
|
1906
|
-
rawInitialPayload,
|
|
2093
|
+
initialPayload: initialPayloadParser(decodeBase64(sourceBody)),
|
|
1907
2094
|
headers: recreateUserHeaders(new Headers(sourceHeader)),
|
|
1908
|
-
steps,
|
|
2095
|
+
steps: [],
|
|
1909
2096
|
url,
|
|
1910
2097
|
failureUrl: url,
|
|
1911
2098
|
debug
|
|
1912
2099
|
});
|
|
1913
|
-
|
|
2100
|
+
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
2101
|
+
routeFunction,
|
|
2102
|
+
workflowContext
|
|
2103
|
+
);
|
|
2104
|
+
if (authCheck.isErr()) {
|
|
2105
|
+
await debug?.log("ERROR", "ERROR", { error: authCheck.error.message });
|
|
2106
|
+
throw authCheck.error;
|
|
2107
|
+
} else if (authCheck.value === "run-ended") {
|
|
2108
|
+
return err(new WorkflowError("Not authorized to run the failure function."));
|
|
2109
|
+
}
|
|
2110
|
+
await failureFunction({
|
|
2111
|
+
context: workflowContext,
|
|
2112
|
+
failStatus: status,
|
|
2113
|
+
failResponse: errorPayload.message,
|
|
2114
|
+
failHeaders: header
|
|
2115
|
+
});
|
|
1914
2116
|
} catch (error) {
|
|
1915
2117
|
return err(error);
|
|
1916
2118
|
}
|
|
1917
2119
|
return ok("is-failure-callback");
|
|
1918
2120
|
};
|
|
1919
2121
|
|
|
1920
|
-
// src/serve/authorization.ts
|
|
1921
|
-
var import_qstash2 = require("@upstash/qstash");
|
|
1922
|
-
var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
|
|
1923
|
-
static disabledMessage = "disabled-qstash-worklfow-run";
|
|
1924
|
-
/**
|
|
1925
|
-
* overwrite the WorkflowContext.addStep method to always raise QStashWorkflowAbort
|
|
1926
|
-
* error in order to stop the execution whenever we encounter a step.
|
|
1927
|
-
*
|
|
1928
|
-
* @param _step
|
|
1929
|
-
*/
|
|
1930
|
-
async addStep(_step) {
|
|
1931
|
-
throw new QStashWorkflowAbort(_DisabledWorkflowContext.disabledMessage);
|
|
1932
|
-
}
|
|
1933
|
-
/**
|
|
1934
|
-
* copies the passed context to create a DisabledWorkflowContext. Then, runs the
|
|
1935
|
-
* route function with the new context.
|
|
1936
|
-
*
|
|
1937
|
-
* - returns "run-ended" if there are no steps found or
|
|
1938
|
-
* if the auth failed and user called `return`
|
|
1939
|
-
* - returns "step-found" if DisabledWorkflowContext.addStep is called.
|
|
1940
|
-
* - if there is another error, returns the error.
|
|
1941
|
-
*
|
|
1942
|
-
* @param routeFunction
|
|
1943
|
-
*/
|
|
1944
|
-
static async tryAuthentication(routeFunction, context) {
|
|
1945
|
-
const disabledContext = new _DisabledWorkflowContext({
|
|
1946
|
-
qstashClient: new import_qstash2.Client({
|
|
1947
|
-
baseUrl: "disabled-client",
|
|
1948
|
-
token: "disabled-client"
|
|
1949
|
-
}),
|
|
1950
|
-
workflowRunId: context.workflowRunId,
|
|
1951
|
-
headers: context.headers,
|
|
1952
|
-
steps: [],
|
|
1953
|
-
url: context.url,
|
|
1954
|
-
failureUrl: context.failureUrl,
|
|
1955
|
-
initialPayload: context.requestPayload,
|
|
1956
|
-
rawInitialPayload: context.rawInitialPayload,
|
|
1957
|
-
env: context.env,
|
|
1958
|
-
retries: context.retries
|
|
1959
|
-
});
|
|
1960
|
-
try {
|
|
1961
|
-
await routeFunction(disabledContext);
|
|
1962
|
-
} catch (error) {
|
|
1963
|
-
if (error instanceof QStashWorkflowAbort && error.stepName === this.disabledMessage) {
|
|
1964
|
-
return ok("step-found");
|
|
1965
|
-
}
|
|
1966
|
-
return err(error);
|
|
1967
|
-
}
|
|
1968
|
-
return ok("run-ended");
|
|
1969
|
-
}
|
|
1970
|
-
};
|
|
1971
|
-
|
|
1972
2122
|
// src/serve/options.ts
|
|
1973
|
-
var import_qstash3 = require("@upstash/qstash");
|
|
1974
2123
|
var import_qstash4 = require("@upstash/qstash");
|
|
2124
|
+
var import_qstash5 = require("@upstash/qstash");
|
|
1975
2125
|
var processOptions = (options) => {
|
|
1976
2126
|
const environment = options?.env ?? (typeof process === "undefined" ? {} : process.env);
|
|
1977
2127
|
const receiverEnvironmentVariablesSet = Boolean(
|
|
1978
2128
|
environment.QSTASH_CURRENT_SIGNING_KEY && environment.QSTASH_NEXT_SIGNING_KEY
|
|
1979
2129
|
);
|
|
1980
2130
|
return {
|
|
1981
|
-
qstashClient: new
|
|
2131
|
+
qstashClient: new import_qstash5.Client({
|
|
1982
2132
|
baseUrl: environment.QSTASH_URL,
|
|
1983
2133
|
token: environment.QSTASH_TOKEN
|
|
1984
2134
|
}),
|
|
@@ -1999,7 +2149,7 @@ var processOptions = (options) => {
|
|
|
1999
2149
|
throw error;
|
|
2000
2150
|
}
|
|
2001
2151
|
},
|
|
2002
|
-
receiver: receiverEnvironmentVariablesSet ? new
|
|
2152
|
+
receiver: receiverEnvironmentVariablesSet ? new import_qstash4.Receiver({
|
|
2003
2153
|
currentSigningKey: environment.QSTASH_CURRENT_SIGNING_KEY,
|
|
2004
2154
|
nextSigningKey: environment.QSTASH_NEXT_SIGNING_KEY
|
|
2005
2155
|
}) : void 0,
|
|
@@ -2061,6 +2211,9 @@ var serve = (routeFunction, options) => {
|
|
|
2061
2211
|
const { rawInitialPayload, steps, isLastDuplicate } = await parseRequest(
|
|
2062
2212
|
requestPayload,
|
|
2063
2213
|
isFirstInvocation,
|
|
2214
|
+
workflowRunId,
|
|
2215
|
+
qstashClient.http,
|
|
2216
|
+
request.headers.get("upstash-message-id"),
|
|
2064
2217
|
debug
|
|
2065
2218
|
);
|
|
2066
2219
|
if (isLastDuplicate) {
|
|
@@ -2071,6 +2224,7 @@ var serve = (routeFunction, options) => {
|
|
|
2071
2224
|
requestPayload,
|
|
2072
2225
|
qstashClient,
|
|
2073
2226
|
initialPayloadParser,
|
|
2227
|
+
routeFunction,
|
|
2074
2228
|
failureFunction
|
|
2075
2229
|
);
|
|
2076
2230
|
if (failureCheck.isErr()) {
|
|
@@ -2083,7 +2237,6 @@ var serve = (routeFunction, options) => {
|
|
|
2083
2237
|
qstashClient,
|
|
2084
2238
|
workflowRunId,
|
|
2085
2239
|
initialPayload: initialPayloadParser(rawInitialPayload),
|
|
2086
|
-
rawInitialPayload,
|
|
2087
2240
|
headers: recreateUserHeaders(request.headers),
|
|
2088
2241
|
steps,
|
|
2089
2242
|
url: workflowUrl,
|
|
@@ -2121,7 +2274,11 @@ var serve = (routeFunction, options) => {
|
|
|
2121
2274
|
onStep: async () => routeFunction(workflowContext),
|
|
2122
2275
|
onCleanup: async () => {
|
|
2123
2276
|
await triggerWorkflowDelete(workflowContext, debug);
|
|
2124
|
-
}
|
|
2277
|
+
},
|
|
2278
|
+
onCancel: async () => {
|
|
2279
|
+
await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
|
|
2280
|
+
},
|
|
2281
|
+
debug
|
|
2125
2282
|
});
|
|
2126
2283
|
if (result.isErr()) {
|
|
2127
2284
|
await debug?.log("ERROR", "ERROR", { error: result.error.message });
|
|
@@ -2147,35 +2304,93 @@ var serve = (routeFunction, options) => {
|
|
|
2147
2304
|
};
|
|
2148
2305
|
|
|
2149
2306
|
// src/client/index.ts
|
|
2150
|
-
var
|
|
2307
|
+
var import_qstash6 = require("@upstash/qstash");
|
|
2151
2308
|
var Client3 = class {
|
|
2152
2309
|
client;
|
|
2153
2310
|
constructor(clientConfig) {
|
|
2154
2311
|
if (!clientConfig.token) {
|
|
2155
|
-
console.
|
|
2312
|
+
console.error(
|
|
2313
|
+
"QStash token is required for Upstash Workflow!\n\nTo fix this:\n1. Get your token from the Upstash Console (https://console.upstash.com/qstash)\n2. Initialize the workflow client with:\n\n const client = new Client({\n token: '<YOUR_QSTASH_TOKEN>'\n });"
|
|
2314
|
+
);
|
|
2156
2315
|
}
|
|
2157
|
-
this.client = new
|
|
2316
|
+
this.client = new import_qstash6.Client(clientConfig);
|
|
2158
2317
|
}
|
|
2159
2318
|
/**
|
|
2160
2319
|
* Cancel an ongoing workflow
|
|
2161
2320
|
*
|
|
2321
|
+
* Returns true if workflow is canceled succesfully. Otherwise, throws error.
|
|
2322
|
+
*
|
|
2323
|
+
* There are multiple ways you can cancel workflows:
|
|
2324
|
+
* - pass one or more workflow run ids to cancel them
|
|
2325
|
+
* - pass a workflow url to cancel all runs starting with this url
|
|
2326
|
+
* - cancel all pending or active workflow runs
|
|
2327
|
+
*
|
|
2328
|
+
* ### Cancel a set of workflow runs
|
|
2329
|
+
*
|
|
2162
2330
|
* ```ts
|
|
2163
|
-
*
|
|
2331
|
+
* // cancel a single workflow
|
|
2332
|
+
* await client.cancel({ ids: "<WORKFLOW_RUN_ID>" })
|
|
2164
2333
|
*
|
|
2165
|
-
*
|
|
2166
|
-
* await client.cancel({
|
|
2334
|
+
* // cancel a set of workflow runs
|
|
2335
|
+
* await client.cancel({ ids: [
|
|
2336
|
+
* "<WORKFLOW_RUN_ID_1>",
|
|
2337
|
+
* "<WORKFLOW_RUN_ID_2>",
|
|
2338
|
+
* ]})
|
|
2339
|
+
* ```
|
|
2340
|
+
*
|
|
2341
|
+
* ### Cancel workflows starting with a url
|
|
2342
|
+
*
|
|
2343
|
+
* If you have an endpoint called `https://your-endpoint.com` and you
|
|
2344
|
+
* want to cancel all workflow runs on it, you can use `urlStartingWith`.
|
|
2345
|
+
*
|
|
2346
|
+
* Note that this will cancel workflows in all endpoints under
|
|
2347
|
+
* `https://your-endpoint.com`.
|
|
2348
|
+
*
|
|
2349
|
+
* ```ts
|
|
2350
|
+
* await client.cancel({ urlStartingWith: "https://your-endpoint.com" })
|
|
2167
2351
|
* ```
|
|
2168
2352
|
*
|
|
2169
|
-
*
|
|
2353
|
+
* ### Cancel *all* workflows
|
|
2354
|
+
*
|
|
2355
|
+
* To cancel all pending and currently running workflows, you can
|
|
2356
|
+
* do it like this:
|
|
2357
|
+
*
|
|
2358
|
+
* ```ts
|
|
2359
|
+
* await client.cancel({ all: true })
|
|
2360
|
+
* ```
|
|
2361
|
+
*
|
|
2362
|
+
* @param ids run id of the workflow to delete
|
|
2363
|
+
* @param urlStartingWith cancel workflows starting with this url. Will be ignored
|
|
2364
|
+
* if `ids` parameter is set.
|
|
2365
|
+
* @param all set to true in order to cancel all workflows. Will be ignored
|
|
2366
|
+
* if `ids` or `urlStartingWith` parameters are set.
|
|
2170
2367
|
* @returns true if workflow is succesfully deleted. Otherwise throws QStashError
|
|
2171
2368
|
*/
|
|
2172
|
-
async cancel({
|
|
2369
|
+
async cancel({
|
|
2370
|
+
ids,
|
|
2371
|
+
urlStartingWith,
|
|
2372
|
+
all
|
|
2373
|
+
}) {
|
|
2374
|
+
let body;
|
|
2375
|
+
if (ids) {
|
|
2376
|
+
const runIdArray = typeof ids === "string" ? [ids] : ids;
|
|
2377
|
+
body = JSON.stringify({ workflowRunIds: runIdArray });
|
|
2378
|
+
} else if (urlStartingWith) {
|
|
2379
|
+
body = JSON.stringify({ workflowUrl: urlStartingWith });
|
|
2380
|
+
} else if (all) {
|
|
2381
|
+
body = "{}";
|
|
2382
|
+
} else {
|
|
2383
|
+
throw new TypeError("The `cancel` method cannot be called without any options.");
|
|
2384
|
+
}
|
|
2173
2385
|
const result = await this.client.http.request({
|
|
2174
|
-
path: ["v2", "workflows", "runs"
|
|
2386
|
+
path: ["v2", "workflows", "runs"],
|
|
2175
2387
|
method: "DELETE",
|
|
2176
|
-
|
|
2388
|
+
body,
|
|
2389
|
+
headers: {
|
|
2390
|
+
"Content-Type": "application/json"
|
|
2391
|
+
}
|
|
2177
2392
|
});
|
|
2178
|
-
return result
|
|
2393
|
+
return result;
|
|
2179
2394
|
}
|
|
2180
2395
|
/**
|
|
2181
2396
|
* Notify a workflow run waiting for an event
|
|
@@ -2220,13 +2435,13 @@ var Client3 = class {
|
|
|
2220
2435
|
* Trigger new workflow run and returns the workflow run id
|
|
2221
2436
|
*
|
|
2222
2437
|
* ```ts
|
|
2223
|
-
* const { workflowRunId } await client.trigger({
|
|
2438
|
+
* const { workflowRunId } = await client.trigger({
|
|
2224
2439
|
* url: "https://workflow-endpoint.com",
|
|
2225
|
-
* body: "hello there!",
|
|
2226
|
-
* headers: { ... },
|
|
2227
|
-
* workflowRunId: "my-workflow", //
|
|
2228
|
-
* retries: 3
|
|
2229
|
-
* })
|
|
2440
|
+
* body: "hello there!", // Optional body
|
|
2441
|
+
* headers: { ... }, // Optional headers
|
|
2442
|
+
* workflowRunId: "my-workflow", // Optional workflow run ID
|
|
2443
|
+
* retries: 3 // Optional retries for the initial request
|
|
2444
|
+
* });
|
|
2230
2445
|
*
|
|
2231
2446
|
* console.log(workflowRunId)
|
|
2232
2447
|
* // wfr_my-workflow
|
|
@@ -2272,10 +2487,10 @@ var Client3 = class {
|
|
|
2272
2487
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2273
2488
|
0 && (module.exports = {
|
|
2274
2489
|
Client,
|
|
2275
|
-
QStashWorkflowAbort,
|
|
2276
|
-
QStashWorkflowError,
|
|
2277
2490
|
StepTypes,
|
|
2491
|
+
WorkflowAbort,
|
|
2278
2492
|
WorkflowContext,
|
|
2493
|
+
WorkflowError,
|
|
2279
2494
|
WorkflowLogger,
|
|
2280
2495
|
serve
|
|
2281
2496
|
});
|