@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/h3.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,14 +15,6 @@ 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
|
// platforms/h3.ts
|
|
@@ -348,22 +338,33 @@ var H3Response = globalThis.Response;
|
|
|
348
338
|
|
|
349
339
|
// src/error.ts
|
|
350
340
|
var import_qstash = require("@upstash/qstash");
|
|
351
|
-
var
|
|
341
|
+
var WorkflowError = class extends import_qstash.QstashError {
|
|
352
342
|
constructor(message) {
|
|
353
343
|
super(message);
|
|
354
|
-
this.name = "
|
|
344
|
+
this.name = "WorkflowError";
|
|
355
345
|
}
|
|
356
346
|
};
|
|
357
|
-
var
|
|
347
|
+
var WorkflowAbort = class extends Error {
|
|
358
348
|
stepInfo;
|
|
359
349
|
stepName;
|
|
360
|
-
|
|
350
|
+
/**
|
|
351
|
+
* whether workflow is to be canceled on abort
|
|
352
|
+
*/
|
|
353
|
+
cancelWorkflow;
|
|
354
|
+
/**
|
|
355
|
+
*
|
|
356
|
+
* @param stepName name of the aborting step
|
|
357
|
+
* @param stepInfo step information
|
|
358
|
+
* @param cancelWorkflow
|
|
359
|
+
*/
|
|
360
|
+
constructor(stepName, stepInfo, cancelWorkflow = false) {
|
|
361
361
|
super(
|
|
362
362
|
`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}'.`
|
|
363
363
|
);
|
|
364
|
-
this.name = "
|
|
364
|
+
this.name = "WorkflowAbort";
|
|
365
365
|
this.stepName = stepName;
|
|
366
366
|
this.stepInfo = stepInfo;
|
|
367
|
+
this.cancelWorkflow = cancelWorkflow;
|
|
367
368
|
}
|
|
368
369
|
};
|
|
369
370
|
var formatWorkflowError = (error) => {
|
|
@@ -385,6 +386,44 @@ var makeNotifyRequest = async (requester, eventId, eventData) => {
|
|
|
385
386
|
});
|
|
386
387
|
return result;
|
|
387
388
|
};
|
|
389
|
+
var makeCancelRequest = async (requester, workflowRunId) => {
|
|
390
|
+
await requester.request({
|
|
391
|
+
path: ["v2", "workflows", "runs", `${workflowRunId}?cancel=true`],
|
|
392
|
+
method: "DELETE",
|
|
393
|
+
parseResponseAsJson: false
|
|
394
|
+
});
|
|
395
|
+
return true;
|
|
396
|
+
};
|
|
397
|
+
var getSteps = async (requester, workflowRunId, messageId, debug) => {
|
|
398
|
+
try {
|
|
399
|
+
const steps = await requester.request({
|
|
400
|
+
path: ["v2", "workflows", "runs", workflowRunId],
|
|
401
|
+
parseResponseAsJson: true
|
|
402
|
+
});
|
|
403
|
+
if (!messageId) {
|
|
404
|
+
await debug?.log("INFO", "ENDPOINT_START", {
|
|
405
|
+
message: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
|
|
406
|
+
});
|
|
407
|
+
return steps;
|
|
408
|
+
} else {
|
|
409
|
+
const index = steps.findIndex((item) => item.messageId === messageId);
|
|
410
|
+
if (index === -1) {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
const filteredSteps = steps.slice(0, index + 1);
|
|
414
|
+
await debug?.log("INFO", "ENDPOINT_START", {
|
|
415
|
+
message: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
|
|
416
|
+
});
|
|
417
|
+
return filteredSteps;
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
await debug?.log("ERROR", "ERROR", {
|
|
421
|
+
message: "failed while fetching steps.",
|
|
422
|
+
error
|
|
423
|
+
});
|
|
424
|
+
throw new WorkflowError(`Failed while pulling steps. ${error}`);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
388
427
|
|
|
389
428
|
// src/context/steps.ts
|
|
390
429
|
var BaseLazyStep = class {
|
|
@@ -999,6 +1038,7 @@ var StepTypes = [
|
|
|
999
1038
|
];
|
|
1000
1039
|
|
|
1001
1040
|
// src/workflow-requests.ts
|
|
1041
|
+
var import_qstash2 = require("@upstash/qstash");
|
|
1002
1042
|
var triggerFirstInvocation = async (workflowContext, retries, debug) => {
|
|
1003
1043
|
const { headers } = getHeaders(
|
|
1004
1044
|
"true",
|
|
@@ -1009,20 +1049,32 @@ var triggerFirstInvocation = async (workflowContext, retries, debug) => {
|
|
|
1009
1049
|
workflowContext.failureUrl,
|
|
1010
1050
|
retries
|
|
1011
1051
|
);
|
|
1012
|
-
await debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
|
|
1013
|
-
headers,
|
|
1014
|
-
requestPayload: workflowContext.requestPayload,
|
|
1015
|
-
url: workflowContext.url
|
|
1016
|
-
});
|
|
1017
1052
|
try {
|
|
1018
1053
|
const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
|
|
1019
|
-
await workflowContext.qstashClient.publish({
|
|
1054
|
+
const result = await workflowContext.qstashClient.publish({
|
|
1020
1055
|
headers,
|
|
1021
1056
|
method: "POST",
|
|
1022
1057
|
body,
|
|
1023
1058
|
url: workflowContext.url
|
|
1024
1059
|
});
|
|
1025
|
-
|
|
1060
|
+
if (result.deduplicated) {
|
|
1061
|
+
await debug?.log("WARN", "SUBMIT_FIRST_INVOCATION", {
|
|
1062
|
+
message: `Workflow run ${workflowContext.workflowRunId} already exists. A new one isn't created.`,
|
|
1063
|
+
headers,
|
|
1064
|
+
requestPayload: workflowContext.requestPayload,
|
|
1065
|
+
url: workflowContext.url,
|
|
1066
|
+
messageId: result.messageId
|
|
1067
|
+
});
|
|
1068
|
+
return ok("workflow-run-already-exists");
|
|
1069
|
+
} else {
|
|
1070
|
+
await debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
|
|
1071
|
+
headers,
|
|
1072
|
+
requestPayload: workflowContext.requestPayload,
|
|
1073
|
+
url: workflowContext.url,
|
|
1074
|
+
messageId: result.messageId
|
|
1075
|
+
});
|
|
1076
|
+
return ok("success");
|
|
1077
|
+
}
|
|
1026
1078
|
} catch (error) {
|
|
1027
1079
|
const error_ = error;
|
|
1028
1080
|
return err(error_);
|
|
@@ -1030,7 +1082,9 @@ var triggerFirstInvocation = async (workflowContext, retries, debug) => {
|
|
|
1030
1082
|
};
|
|
1031
1083
|
var triggerRouteFunction = async ({
|
|
1032
1084
|
onCleanup,
|
|
1033
|
-
onStep
|
|
1085
|
+
onStep,
|
|
1086
|
+
onCancel,
|
|
1087
|
+
debug
|
|
1034
1088
|
}) => {
|
|
1035
1089
|
try {
|
|
1036
1090
|
await onStep();
|
|
@@ -1038,19 +1092,50 @@ var triggerRouteFunction = async ({
|
|
|
1038
1092
|
return ok("workflow-finished");
|
|
1039
1093
|
} catch (error) {
|
|
1040
1094
|
const error_ = error;
|
|
1041
|
-
|
|
1095
|
+
if (error instanceof import_qstash2.QstashError && error.status === 400) {
|
|
1096
|
+
await debug?.log("WARN", "RESPONSE_WORKFLOW", {
|
|
1097
|
+
message: `tried to append to a cancelled workflow. exiting without publishing.`,
|
|
1098
|
+
name: error.name,
|
|
1099
|
+
errorMessage: error.message
|
|
1100
|
+
});
|
|
1101
|
+
return ok("workflow-was-finished");
|
|
1102
|
+
} else if (!(error_ instanceof WorkflowAbort)) {
|
|
1103
|
+
return err(error_);
|
|
1104
|
+
} else if (error_.cancelWorkflow) {
|
|
1105
|
+
await onCancel();
|
|
1106
|
+
return ok("workflow-finished");
|
|
1107
|
+
} else {
|
|
1108
|
+
return ok("step-finished");
|
|
1109
|
+
}
|
|
1042
1110
|
}
|
|
1043
1111
|
};
|
|
1044
1112
|
var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
|
|
1045
1113
|
await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
|
|
1046
1114
|
deletedWorkflowRunId: workflowContext.workflowRunId
|
|
1047
1115
|
});
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1116
|
+
try {
|
|
1117
|
+
await workflowContext.qstashClient.http.request({
|
|
1118
|
+
path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
|
|
1119
|
+
method: "DELETE",
|
|
1120
|
+
parseResponseAsJson: false
|
|
1121
|
+
});
|
|
1122
|
+
await debug?.log(
|
|
1123
|
+
"SUBMIT",
|
|
1124
|
+
"SUBMIT_CLEANUP",
|
|
1125
|
+
`workflow run ${workflowContext.workflowRunId} deleted.`
|
|
1126
|
+
);
|
|
1127
|
+
return { deleted: true };
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
if (error instanceof import_qstash2.QstashError && error.status === 404) {
|
|
1130
|
+
await debug?.log("WARN", "SUBMIT_CLEANUP", {
|
|
1131
|
+
message: `Failed to remove workflow run ${workflowContext.workflowRunId} as it doesn't exist.`,
|
|
1132
|
+
name: error.name,
|
|
1133
|
+
errorMessage: error.message
|
|
1134
|
+
});
|
|
1135
|
+
return { deleted: false };
|
|
1136
|
+
}
|
|
1137
|
+
throw error;
|
|
1138
|
+
}
|
|
1054
1139
|
};
|
|
1055
1140
|
var recreateUserHeaders = (headers) => {
|
|
1056
1141
|
const filteredHeaders = new Headers();
|
|
@@ -1066,15 +1151,32 @@ var recreateUserHeaders = (headers) => {
|
|
|
1066
1151
|
var handleThirdPartyCallResult = async (request, requestPayload, client, workflowUrl, failureUrl, retries, debug) => {
|
|
1067
1152
|
try {
|
|
1068
1153
|
if (request.headers.get("Upstash-Workflow-Callback")) {
|
|
1069
|
-
|
|
1154
|
+
let callbackPayload;
|
|
1155
|
+
if (requestPayload) {
|
|
1156
|
+
callbackPayload = requestPayload;
|
|
1157
|
+
} else {
|
|
1158
|
+
const workflowRunId2 = request.headers.get("upstash-workflow-runid");
|
|
1159
|
+
const messageId = request.headers.get("upstash-message-id");
|
|
1160
|
+
if (!workflowRunId2)
|
|
1161
|
+
throw new WorkflowError("workflow run id missing in context.call lazy fetch.");
|
|
1162
|
+
if (!messageId) throw new WorkflowError("message id missing in context.call lazy fetch.");
|
|
1163
|
+
const steps = await getSteps(client.http, workflowRunId2, messageId, debug);
|
|
1164
|
+
const failingStep = steps.find((step) => step.messageId === messageId);
|
|
1165
|
+
if (!failingStep)
|
|
1166
|
+
throw new WorkflowError(
|
|
1167
|
+
"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.`)
|
|
1168
|
+
);
|
|
1169
|
+
callbackPayload = atob(failingStep.body);
|
|
1170
|
+
}
|
|
1171
|
+
const callbackMessage = JSON.parse(callbackPayload);
|
|
1070
1172
|
if (!(callbackMessage.status >= 200 && callbackMessage.status < 300) && callbackMessage.maxRetries && callbackMessage.retried !== callbackMessage.maxRetries) {
|
|
1071
1173
|
await debug?.log("WARN", "SUBMIT_THIRD_PARTY_RESULT", {
|
|
1072
1174
|
status: callbackMessage.status,
|
|
1073
|
-
body: atob(callbackMessage.body)
|
|
1175
|
+
body: atob(callbackMessage.body ?? "")
|
|
1074
1176
|
});
|
|
1075
1177
|
console.warn(
|
|
1076
1178
|
`Workflow Warning: "context.call" failed with status ${callbackMessage.status} and will retry (retried ${callbackMessage.retried ?? 0} out of ${callbackMessage.maxRetries} times). Error Message:
|
|
1077
|
-
${atob(callbackMessage.body)}`
|
|
1179
|
+
${atob(callbackMessage.body ?? "")}`
|
|
1078
1180
|
);
|
|
1079
1181
|
return ok("call-will-retry");
|
|
1080
1182
|
}
|
|
@@ -1108,7 +1210,7 @@ ${atob(callbackMessage.body)}`
|
|
|
1108
1210
|
);
|
|
1109
1211
|
const callResponse = {
|
|
1110
1212
|
status: callbackMessage.status,
|
|
1111
|
-
body: atob(callbackMessage.body),
|
|
1213
|
+
body: atob(callbackMessage.body ?? ""),
|
|
1112
1214
|
header: callbackMessage.header
|
|
1113
1215
|
};
|
|
1114
1216
|
const callResultStep = {
|
|
@@ -1139,9 +1241,7 @@ ${atob(callbackMessage.body)}`
|
|
|
1139
1241
|
} catch (error) {
|
|
1140
1242
|
const isCallReturn = request.headers.get("Upstash-Workflow-Callback");
|
|
1141
1243
|
return err(
|
|
1142
|
-
new
|
|
1143
|
-
`Error when handling call return (isCallReturn=${isCallReturn}): ${error}`
|
|
1144
|
-
)
|
|
1244
|
+
new WorkflowError(`Error when handling call return (isCallReturn=${isCallReturn}): ${error}`)
|
|
1145
1245
|
);
|
|
1146
1246
|
}
|
|
1147
1247
|
};
|
|
@@ -1149,7 +1249,8 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
|
|
|
1149
1249
|
const baseHeaders = {
|
|
1150
1250
|
[WORKFLOW_INIT_HEADER]: initHeaderValue,
|
|
1151
1251
|
[WORKFLOW_ID_HEADER]: workflowRunId,
|
|
1152
|
-
[WORKFLOW_URL_HEADER]: workflowUrl
|
|
1252
|
+
[WORKFLOW_URL_HEADER]: workflowUrl,
|
|
1253
|
+
[WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody"
|
|
1153
1254
|
};
|
|
1154
1255
|
if (!step?.callUrl) {
|
|
1155
1256
|
baseHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
|
|
@@ -1162,8 +1263,8 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
|
|
|
1162
1263
|
}
|
|
1163
1264
|
if (step?.callUrl) {
|
|
1164
1265
|
baseHeaders["Upstash-Retries"] = callRetries?.toString() ?? "0";
|
|
1165
|
-
baseHeaders[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete";
|
|
1166
|
-
if (retries) {
|
|
1266
|
+
baseHeaders[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete,InitialBody";
|
|
1267
|
+
if (retries !== void 0) {
|
|
1167
1268
|
baseHeaders["Upstash-Callback-Retries"] = retries.toString();
|
|
1168
1269
|
baseHeaders["Upstash-Failure-Callback-Retries"] = retries.toString();
|
|
1169
1270
|
}
|
|
@@ -1198,6 +1299,7 @@ var getHeaders = (initHeaderValue, workflowRunId, workflowUrl, userHeaders, step
|
|
|
1198
1299
|
"Upstash-Callback-Workflow-CallType": "fromCallback",
|
|
1199
1300
|
"Upstash-Callback-Workflow-Init": "false",
|
|
1200
1301
|
"Upstash-Callback-Workflow-Url": workflowUrl,
|
|
1302
|
+
"Upstash-Callback-Feature-Set": "LazyFetch,InitialBody",
|
|
1201
1303
|
"Upstash-Callback-Forward-Upstash-Workflow-Callback": "true",
|
|
1202
1304
|
"Upstash-Callback-Forward-Upstash-Workflow-StepId": step.stepId.toString(),
|
|
1203
1305
|
"Upstash-Callback-Forward-Upstash-Workflow-StepName": step.stepName,
|
|
@@ -1246,7 +1348,7 @@ var verifyRequest = async (body, signature, verifier) => {
|
|
|
1246
1348
|
throw new Error("Signature in `Upstash-Signature` header is not valid");
|
|
1247
1349
|
}
|
|
1248
1350
|
} catch (error) {
|
|
1249
|
-
throw new
|
|
1351
|
+
throw new WorkflowError(
|
|
1250
1352
|
`Failed to verify that the Workflow request comes from QStash: ${error}
|
|
1251
1353
|
|
|
1252
1354
|
If signature is missing, trigger the workflow endpoint by publishing your request to QStash instead of calling it directly.
|
|
@@ -1286,14 +1388,14 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1286
1388
|
*
|
|
1287
1389
|
* If a function is already executing (this.executingStep), this
|
|
1288
1390
|
* means that there is a nested step which is not allowed. In this
|
|
1289
|
-
* case, addStep throws
|
|
1391
|
+
* case, addStep throws WorkflowError.
|
|
1290
1392
|
*
|
|
1291
1393
|
* @param stepInfo step plan to add
|
|
1292
1394
|
* @returns result of the step function
|
|
1293
1395
|
*/
|
|
1294
1396
|
async addStep(stepInfo) {
|
|
1295
1397
|
if (this.executingStep) {
|
|
1296
|
-
throw new
|
|
1398
|
+
throw new WorkflowError(
|
|
1297
1399
|
`A step can not be run inside another step. Tried to run '${stepInfo.stepName}' inside '${this.executingStep}'`
|
|
1298
1400
|
);
|
|
1299
1401
|
}
|
|
@@ -1378,7 +1480,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1378
1480
|
const sortedSteps = sortSteps(this.steps);
|
|
1379
1481
|
const plannedParallelStepCount = sortedSteps[initialStepCount + this.planStepCount]?.concurrent;
|
|
1380
1482
|
if (parallelCallState !== "first" && plannedParallelStepCount !== parallelSteps.length) {
|
|
1381
|
-
throw new
|
|
1483
|
+
throw new WorkflowError(
|
|
1382
1484
|
`Incompatible number of parallel steps when call state was '${parallelCallState}'. Expected ${parallelSteps.length}, got ${plannedParallelStepCount} from the request.`
|
|
1383
1485
|
);
|
|
1384
1486
|
}
|
|
@@ -1400,7 +1502,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1400
1502
|
case "partial": {
|
|
1401
1503
|
const planStep = this.steps.at(-1);
|
|
1402
1504
|
if (!planStep || planStep.targetStep === void 0) {
|
|
1403
|
-
throw new
|
|
1505
|
+
throw new WorkflowError(
|
|
1404
1506
|
`There must be a last step and it should have targetStep larger than 0.Received: ${JSON.stringify(planStep)}`
|
|
1405
1507
|
);
|
|
1406
1508
|
}
|
|
@@ -1414,17 +1516,17 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1414
1516
|
);
|
|
1415
1517
|
await this.submitStepsToQStash([resultStep], [parallelStep]);
|
|
1416
1518
|
} catch (error) {
|
|
1417
|
-
if (error instanceof
|
|
1519
|
+
if (error instanceof WorkflowAbort) {
|
|
1418
1520
|
throw error;
|
|
1419
1521
|
}
|
|
1420
|
-
throw new
|
|
1522
|
+
throw new WorkflowError(
|
|
1421
1523
|
`Error submitting steps to QStash in partial parallel step execution: ${error}`
|
|
1422
1524
|
);
|
|
1423
1525
|
}
|
|
1424
1526
|
break;
|
|
1425
1527
|
}
|
|
1426
1528
|
case "discard": {
|
|
1427
|
-
throw new
|
|
1529
|
+
throw new WorkflowAbort("discarded parallel");
|
|
1428
1530
|
}
|
|
1429
1531
|
case "last": {
|
|
1430
1532
|
const parallelResultSteps = sortedSteps.filter((step) => step.stepId >= initialStepCount).slice(0, parallelSteps.length);
|
|
@@ -1475,7 +1577,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1475
1577
|
*/
|
|
1476
1578
|
async submitStepsToQStash(steps, lazySteps) {
|
|
1477
1579
|
if (steps.length === 0) {
|
|
1478
|
-
throw new
|
|
1580
|
+
throw new WorkflowError(
|
|
1479
1581
|
`Unable to submit steps to QStash. Provided list is empty. Current step: ${this.stepCount}`
|
|
1480
1582
|
);
|
|
1481
1583
|
}
|
|
@@ -1515,7 +1617,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1515
1617
|
method: "POST",
|
|
1516
1618
|
parseResponseAsJson: false
|
|
1517
1619
|
});
|
|
1518
|
-
throw new
|
|
1620
|
+
throw new WorkflowAbort(steps[0].stepName, steps[0]);
|
|
1519
1621
|
}
|
|
1520
1622
|
const result = await this.context.qstashClient.batchJSON(
|
|
1521
1623
|
steps.map((singleStep, index) => {
|
|
@@ -1567,7 +1669,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1567
1669
|
};
|
|
1568
1670
|
})
|
|
1569
1671
|
});
|
|
1570
|
-
throw new
|
|
1672
|
+
throw new WorkflowAbort(steps[0].stepName, steps[0]);
|
|
1571
1673
|
}
|
|
1572
1674
|
/**
|
|
1573
1675
|
* Get the promise by executing the lazt steps list. If there is a single
|
|
@@ -1592,7 +1694,7 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1592
1694
|
} else if (Array.isArray(result) && lazyStepList.length === result.length && index < lazyStepList.length) {
|
|
1593
1695
|
return result[index];
|
|
1594
1696
|
} else {
|
|
1595
|
-
throw new
|
|
1697
|
+
throw new WorkflowError(
|
|
1596
1698
|
`Unexpected parallel call result while executing step ${index}: '${result}'. Expected ${lazyStepList.length} many items`
|
|
1597
1699
|
);
|
|
1598
1700
|
}
|
|
@@ -1604,12 +1706,12 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
1604
1706
|
};
|
|
1605
1707
|
var validateStep = (lazyStep, stepFromRequest) => {
|
|
1606
1708
|
if (lazyStep.stepName !== stepFromRequest.stepName) {
|
|
1607
|
-
throw new
|
|
1709
|
+
throw new WorkflowError(
|
|
1608
1710
|
`Incompatible step name. Expected '${lazyStep.stepName}', got '${stepFromRequest.stepName}' from the request`
|
|
1609
1711
|
);
|
|
1610
1712
|
}
|
|
1611
1713
|
if (lazyStep.stepType !== stepFromRequest.stepType) {
|
|
1612
|
-
throw new
|
|
1714
|
+
throw new WorkflowError(
|
|
1613
1715
|
`Incompatible step type. Expected '${lazyStep.stepType}', got '${stepFromRequest.stepType}' from the request`
|
|
1614
1716
|
);
|
|
1615
1717
|
}
|
|
@@ -1620,12 +1722,12 @@ var validateParallelSteps = (lazySteps, stepsFromRequest) => {
|
|
|
1620
1722
|
validateStep(lazySteps[index], stepFromRequest);
|
|
1621
1723
|
}
|
|
1622
1724
|
} catch (error) {
|
|
1623
|
-
if (error instanceof
|
|
1725
|
+
if (error instanceof WorkflowError) {
|
|
1624
1726
|
const lazyStepNames = lazySteps.map((lazyStep) => lazyStep.stepName);
|
|
1625
1727
|
const lazyStepTypes = lazySteps.map((lazyStep) => lazyStep.stepType);
|
|
1626
1728
|
const requestStepNames = stepsFromRequest.map((step) => step.stepName);
|
|
1627
1729
|
const requestStepTypes = stepsFromRequest.map((step) => step.stepType);
|
|
1628
|
-
throw new
|
|
1730
|
+
throw new WorkflowError(
|
|
1629
1731
|
`Incompatible steps detected in parallel execution: ${error.message}
|
|
1630
1732
|
> Step Names from the request: ${JSON.stringify(requestStepNames)}
|
|
1631
1733
|
Step Types from the request: ${JSON.stringify(requestStepTypes)}
|
|
@@ -1738,10 +1840,6 @@ var WorkflowContext = class {
|
|
|
1738
1840
|
* headers of the initial request
|
|
1739
1841
|
*/
|
|
1740
1842
|
headers;
|
|
1741
|
-
/**
|
|
1742
|
-
* initial payload as a raw string
|
|
1743
|
-
*/
|
|
1744
|
-
rawInitialPayload;
|
|
1745
1843
|
/**
|
|
1746
1844
|
* Map of environment variables and their values.
|
|
1747
1845
|
*
|
|
@@ -1776,7 +1874,6 @@ var WorkflowContext = class {
|
|
|
1776
1874
|
failureUrl,
|
|
1777
1875
|
debug,
|
|
1778
1876
|
initialPayload,
|
|
1779
|
-
rawInitialPayload,
|
|
1780
1877
|
env,
|
|
1781
1878
|
retries
|
|
1782
1879
|
}) {
|
|
@@ -1787,7 +1884,6 @@ var WorkflowContext = class {
|
|
|
1787
1884
|
this.failureUrl = failureUrl;
|
|
1788
1885
|
this.headers = headers;
|
|
1789
1886
|
this.requestPayload = initialPayload;
|
|
1790
|
-
this.rawInitialPayload = rawInitialPayload ?? JSON.stringify(this.requestPayload);
|
|
1791
1887
|
this.env = env ?? {};
|
|
1792
1888
|
this.retries = retries ?? DEFAULT_RETRIES;
|
|
1793
1889
|
this.executor = new AutoExecutor(this, this.steps, debug);
|
|
@@ -1808,7 +1904,7 @@ var WorkflowContext = class {
|
|
|
1808
1904
|
* const [result1, result2] = await Promise.all([
|
|
1809
1905
|
* context.run("step 1", () => {
|
|
1810
1906
|
* return "result1"
|
|
1811
|
-
* })
|
|
1907
|
+
* }),
|
|
1812
1908
|
* context.run("step 2", async () => {
|
|
1813
1909
|
* return await fetchResults()
|
|
1814
1910
|
* })
|
|
@@ -1826,6 +1922,10 @@ var WorkflowContext = class {
|
|
|
1826
1922
|
/**
|
|
1827
1923
|
* Stops the execution for the duration provided.
|
|
1828
1924
|
*
|
|
1925
|
+
* ```typescript
|
|
1926
|
+
* await context.sleep('sleep1', 3) // wait for three seconds
|
|
1927
|
+
* ```
|
|
1928
|
+
*
|
|
1829
1929
|
* @param stepName
|
|
1830
1930
|
* @param duration sleep duration in seconds
|
|
1831
1931
|
* @returns undefined
|
|
@@ -1836,6 +1936,10 @@ var WorkflowContext = class {
|
|
|
1836
1936
|
/**
|
|
1837
1937
|
* Stops the execution until the date time provided.
|
|
1838
1938
|
*
|
|
1939
|
+
* ```typescript
|
|
1940
|
+
* await context.sleepUntil('sleep1', Date.now() / 1000 + 3) // wait for three seconds
|
|
1941
|
+
* ```
|
|
1942
|
+
*
|
|
1839
1943
|
* @param stepName
|
|
1840
1944
|
* @param datetime time to sleep until. Can be provided as a number (in unix seconds),
|
|
1841
1945
|
* as a Date object or a string (passed to `new Date(datetimeString)`)
|
|
@@ -1859,7 +1963,7 @@ var WorkflowContext = class {
|
|
|
1859
1963
|
* const { status, body } = await context.call<string>(
|
|
1860
1964
|
* "post call step",
|
|
1861
1965
|
* {
|
|
1862
|
-
* url:
|
|
1966
|
+
* url: "https://www.some-endpoint.com/api",
|
|
1863
1967
|
* method: "POST",
|
|
1864
1968
|
* body: "my-payload"
|
|
1865
1969
|
* }
|
|
@@ -1913,45 +2017,43 @@ var WorkflowContext = class {
|
|
|
1913
2017
|
}
|
|
1914
2018
|
}
|
|
1915
2019
|
/**
|
|
1916
|
-
*
|
|
1917
|
-
* timeout ends
|
|
2020
|
+
* Pauses workflow execution until a specific event occurs or a timeout is reached.
|
|
1918
2021
|
*
|
|
1919
|
-
|
|
1920
|
-
* const
|
|
1921
|
-
*
|
|
1922
|
-
*
|
|
1923
|
-
|
|
1924
|
-
* );
|
|
1925
|
-
* ```
|
|
2022
|
+
*```ts
|
|
2023
|
+
* const result = await workflow.waitForEvent("payment-confirmed", {
|
|
2024
|
+
* timeout: "5m"
|
|
2025
|
+
* });
|
|
2026
|
+
*```
|
|
1926
2027
|
*
|
|
1927
|
-
* To notify a waiting workflow
|
|
2028
|
+
* To notify a waiting workflow:
|
|
1928
2029
|
*
|
|
1929
2030
|
* ```ts
|
|
1930
2031
|
* import { Client } from "@upstash/workflow";
|
|
1931
2032
|
*
|
|
1932
|
-
* const client = new Client({ token: });
|
|
2033
|
+
* const client = new Client({ token: "<QSTASH_TOKEN>" });
|
|
1933
2034
|
*
|
|
1934
2035
|
* await client.notify({
|
|
1935
|
-
* eventId: "
|
|
1936
|
-
*
|
|
2036
|
+
* eventId: "payment.confirmed",
|
|
2037
|
+
* data: {
|
|
2038
|
+
* amount: 99.99,
|
|
2039
|
+
* currency: "USD"
|
|
2040
|
+
* }
|
|
1937
2041
|
* })
|
|
1938
2042
|
* ```
|
|
1939
2043
|
*
|
|
2044
|
+
* Alternatively, you can use the `context.notify` method.
|
|
2045
|
+
*
|
|
1940
2046
|
* @param stepName
|
|
1941
|
-
* @param eventId
|
|
1942
|
-
* @param
|
|
1943
|
-
* @returns
|
|
1944
|
-
* timeout
|
|
1945
|
-
* is the
|
|
2047
|
+
* @param eventId - Unique identifier for the event to wait for
|
|
2048
|
+
* @param options - Configuration options.
|
|
2049
|
+
* @returns `{ timeout: boolean, eventData: unknown }`.
|
|
2050
|
+
* The `timeout` property specifies if the workflow has timed out. The `eventData`
|
|
2051
|
+
* is the data passed when notifying this workflow of an event.
|
|
1946
2052
|
*/
|
|
1947
|
-
async waitForEvent(stepName, eventId,
|
|
1948
|
-
const
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
eventId,
|
|
1952
|
-
typeof timeout === "string" ? timeout : `${timeout}s`
|
|
1953
|
-
)
|
|
1954
|
-
);
|
|
2053
|
+
async waitForEvent(stepName, eventId, options = {}) {
|
|
2054
|
+
const { timeout = "7d" } = options;
|
|
2055
|
+
const timeoutStr = typeof timeout === "string" ? timeout : `${timeout}s`;
|
|
2056
|
+
const result = await this.addStep(new LazyWaitForEventStep(stepName, eventId, timeoutStr));
|
|
1955
2057
|
try {
|
|
1956
2058
|
return {
|
|
1957
2059
|
...result,
|
|
@@ -1961,6 +2063,27 @@ var WorkflowContext = class {
|
|
|
1961
2063
|
return result;
|
|
1962
2064
|
}
|
|
1963
2065
|
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Notify workflow runs waiting for an event
|
|
2068
|
+
*
|
|
2069
|
+
* ```ts
|
|
2070
|
+
* const { eventId, eventData, notifyResponse } = await context.notify(
|
|
2071
|
+
* "notify step", "event-id", "event-data"
|
|
2072
|
+
* );
|
|
2073
|
+
* ```
|
|
2074
|
+
*
|
|
2075
|
+
* Upon `context.notify`, the workflow runs waiting for the given eventId (context.waitForEvent)
|
|
2076
|
+
* will receive the given event data and resume execution.
|
|
2077
|
+
*
|
|
2078
|
+
* The response includes the same eventId and eventData. Additionally, there is
|
|
2079
|
+
* a notifyResponse field which contains a list of `Waiter` objects, each corresponding
|
|
2080
|
+
* to a notified workflow run.
|
|
2081
|
+
*
|
|
2082
|
+
* @param stepName
|
|
2083
|
+
* @param eventId event id to notify
|
|
2084
|
+
* @param eventData event data to notify with
|
|
2085
|
+
* @returns notify response which has event id, event data and list of waiters which were notified
|
|
2086
|
+
*/
|
|
1964
2087
|
async notify(stepName, eventId, eventData) {
|
|
1965
2088
|
const result = await this.addStep(
|
|
1966
2089
|
new LazyNotifyStep(stepName, eventId, eventData, this.qstashClient.http)
|
|
@@ -1974,6 +2097,15 @@ var WorkflowContext = class {
|
|
|
1974
2097
|
return result;
|
|
1975
2098
|
}
|
|
1976
2099
|
}
|
|
2100
|
+
/**
|
|
2101
|
+
* Cancel the current workflow run
|
|
2102
|
+
*
|
|
2103
|
+
* Will throw WorkflowAbort to stop workflow execution.
|
|
2104
|
+
* Shouldn't be inside try/catch.
|
|
2105
|
+
*/
|
|
2106
|
+
async cancel() {
|
|
2107
|
+
throw new WorkflowAbort("cancel", void 0, true);
|
|
2108
|
+
}
|
|
1977
2109
|
/**
|
|
1978
2110
|
* Adds steps to the executor. Needed so that it can be overwritten in
|
|
1979
2111
|
* DisabledWorkflowContext.
|
|
@@ -2014,7 +2146,8 @@ var WorkflowLogger = class _WorkflowLogger {
|
|
|
2014
2146
|
}
|
|
2015
2147
|
writeToConsole(logEntry) {
|
|
2016
2148
|
const JSON_SPACING = 2;
|
|
2017
|
-
console.
|
|
2149
|
+
const logMethod = logEntry.logLevel === "ERROR" ? console.error : logEntry.logLevel === "WARN" ? console.warn : console.log;
|
|
2150
|
+
logMethod(JSON.stringify(logEntry, void 0, JSON_SPACING));
|
|
2018
2151
|
}
|
|
2019
2152
|
shouldLog(level) {
|
|
2020
2153
|
return LOG_LEVELS.indexOf(level) >= LOG_LEVELS.indexOf(this.options.logLevel);
|
|
@@ -2032,11 +2165,13 @@ var WorkflowLogger = class _WorkflowLogger {
|
|
|
2032
2165
|
};
|
|
2033
2166
|
|
|
2034
2167
|
// src/utils.ts
|
|
2035
|
-
var import_node_crypto = __toESM(require("crypto"));
|
|
2036
2168
|
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
2037
2169
|
var NANOID_LENGTH = 21;
|
|
2170
|
+
function getRandomInt() {
|
|
2171
|
+
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
2172
|
+
}
|
|
2038
2173
|
function nanoid() {
|
|
2039
|
-
return
|
|
2174
|
+
return Array.from({ length: NANOID_LENGTH }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
2040
2175
|
}
|
|
2041
2176
|
function getWorkflowRunId(id) {
|
|
2042
2177
|
return `wfr_${id ?? nanoid()}`;
|
|
@@ -2054,6 +2189,63 @@ function decodeBase64(base64) {
|
|
|
2054
2189
|
}
|
|
2055
2190
|
}
|
|
2056
2191
|
|
|
2192
|
+
// src/serve/authorization.ts
|
|
2193
|
+
var import_qstash3 = require("@upstash/qstash");
|
|
2194
|
+
var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
|
|
2195
|
+
static disabledMessage = "disabled-qstash-worklfow-run";
|
|
2196
|
+
/**
|
|
2197
|
+
* overwrite the WorkflowContext.addStep method to always raise WorkflowAbort
|
|
2198
|
+
* error in order to stop the execution whenever we encounter a step.
|
|
2199
|
+
*
|
|
2200
|
+
* @param _step
|
|
2201
|
+
*/
|
|
2202
|
+
async addStep(_step) {
|
|
2203
|
+
throw new WorkflowAbort(_DisabledWorkflowContext.disabledMessage);
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* overwrite cancel method to do nothing
|
|
2207
|
+
*/
|
|
2208
|
+
async cancel() {
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* copies the passed context to create a DisabledWorkflowContext. Then, runs the
|
|
2213
|
+
* route function with the new context.
|
|
2214
|
+
*
|
|
2215
|
+
* - returns "run-ended" if there are no steps found or
|
|
2216
|
+
* if the auth failed and user called `return`
|
|
2217
|
+
* - returns "step-found" if DisabledWorkflowContext.addStep is called.
|
|
2218
|
+
* - if there is another error, returns the error.
|
|
2219
|
+
*
|
|
2220
|
+
* @param routeFunction
|
|
2221
|
+
*/
|
|
2222
|
+
static async tryAuthentication(routeFunction, context) {
|
|
2223
|
+
const disabledContext = new _DisabledWorkflowContext({
|
|
2224
|
+
qstashClient: new import_qstash3.Client({
|
|
2225
|
+
baseUrl: "disabled-client",
|
|
2226
|
+
token: "disabled-client"
|
|
2227
|
+
}),
|
|
2228
|
+
workflowRunId: context.workflowRunId,
|
|
2229
|
+
headers: context.headers,
|
|
2230
|
+
steps: [],
|
|
2231
|
+
url: context.url,
|
|
2232
|
+
failureUrl: context.failureUrl,
|
|
2233
|
+
initialPayload: context.requestPayload,
|
|
2234
|
+
env: context.env,
|
|
2235
|
+
retries: context.retries
|
|
2236
|
+
});
|
|
2237
|
+
try {
|
|
2238
|
+
await routeFunction(disabledContext);
|
|
2239
|
+
} catch (error) {
|
|
2240
|
+
if (error instanceof WorkflowAbort && error.stepName === this.disabledMessage) {
|
|
2241
|
+
return ok("step-found");
|
|
2242
|
+
}
|
|
2243
|
+
return err(error);
|
|
2244
|
+
}
|
|
2245
|
+
return ok("run-ended");
|
|
2246
|
+
}
|
|
2247
|
+
};
|
|
2248
|
+
|
|
2057
2249
|
// src/workflow-parser.ts
|
|
2058
2250
|
var getPayload = async (request) => {
|
|
2059
2251
|
try {
|
|
@@ -2062,8 +2254,8 @@ var getPayload = async (request) => {
|
|
|
2062
2254
|
return;
|
|
2063
2255
|
}
|
|
2064
2256
|
};
|
|
2065
|
-
var
|
|
2066
|
-
const [encodedInitialPayload, ...encodedSteps] =
|
|
2257
|
+
var processRawSteps = (rawSteps) => {
|
|
2258
|
+
const [encodedInitialPayload, ...encodedSteps] = rawSteps;
|
|
2067
2259
|
const rawInitialPayload = decodeBase64(encodedInitialPayload.body);
|
|
2068
2260
|
const initialStep = {
|
|
2069
2261
|
stepId: 0,
|
|
@@ -2073,27 +2265,21 @@ var parsePayload = async (rawPayload, debug) => {
|
|
|
2073
2265
|
concurrent: NO_CONCURRENCY
|
|
2074
2266
|
};
|
|
2075
2267
|
const stepsToDecode = encodedSteps.filter((step) => step.callType === "step");
|
|
2076
|
-
const otherSteps =
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
};
|
|
2092
|
-
step.out = newOut;
|
|
2093
|
-
}
|
|
2094
|
-
return step;
|
|
2095
|
-
})
|
|
2096
|
-
);
|
|
2268
|
+
const otherSteps = stepsToDecode.map((rawStep) => {
|
|
2269
|
+
const step = JSON.parse(decodeBase64(rawStep.body));
|
|
2270
|
+
try {
|
|
2271
|
+
step.out = JSON.parse(step.out);
|
|
2272
|
+
} catch {
|
|
2273
|
+
}
|
|
2274
|
+
if (step.waitEventId) {
|
|
2275
|
+
const newOut = {
|
|
2276
|
+
eventData: step.out ? decodeBase64(step.out) : void 0,
|
|
2277
|
+
timeout: step.waitTimeout ?? false
|
|
2278
|
+
};
|
|
2279
|
+
step.out = newOut;
|
|
2280
|
+
}
|
|
2281
|
+
return step;
|
|
2282
|
+
});
|
|
2097
2283
|
const steps = [initialStep, ...otherSteps];
|
|
2098
2284
|
return {
|
|
2099
2285
|
rawInitialPayload,
|
|
@@ -2141,20 +2327,20 @@ var validateRequest = (request) => {
|
|
|
2141
2327
|
const versionHeader = request.headers.get(WORKFLOW_PROTOCOL_VERSION_HEADER);
|
|
2142
2328
|
const isFirstInvocation = !versionHeader;
|
|
2143
2329
|
if (!isFirstInvocation && versionHeader !== WORKFLOW_PROTOCOL_VERSION) {
|
|
2144
|
-
throw new
|
|
2330
|
+
throw new WorkflowError(
|
|
2145
2331
|
`Incompatible workflow sdk protocol version. Expected ${WORKFLOW_PROTOCOL_VERSION}, got ${versionHeader} from the request.`
|
|
2146
2332
|
);
|
|
2147
2333
|
}
|
|
2148
2334
|
const workflowRunId = isFirstInvocation ? getWorkflowRunId() : request.headers.get(WORKFLOW_ID_HEADER) ?? "";
|
|
2149
2335
|
if (workflowRunId.length === 0) {
|
|
2150
|
-
throw new
|
|
2336
|
+
throw new WorkflowError("Couldn't get workflow id from header");
|
|
2151
2337
|
}
|
|
2152
2338
|
return {
|
|
2153
2339
|
isFirstInvocation,
|
|
2154
2340
|
workflowRunId
|
|
2155
2341
|
};
|
|
2156
2342
|
};
|
|
2157
|
-
var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
|
|
2343
|
+
var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requester, messageId, debug) => {
|
|
2158
2344
|
if (isFirstInvocation) {
|
|
2159
2345
|
return {
|
|
2160
2346
|
rawInitialPayload: requestPayload ?? "",
|
|
@@ -2162,10 +2348,18 @@ var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
|
|
|
2162
2348
|
isLastDuplicate: false
|
|
2163
2349
|
};
|
|
2164
2350
|
} else {
|
|
2351
|
+
let rawSteps;
|
|
2165
2352
|
if (!requestPayload) {
|
|
2166
|
-
|
|
2353
|
+
await debug?.log(
|
|
2354
|
+
"INFO",
|
|
2355
|
+
"ENDPOINT_START",
|
|
2356
|
+
"request payload is empty, steps will be fetched from QStash."
|
|
2357
|
+
);
|
|
2358
|
+
rawSteps = await getSteps(requester, workflowRunId, messageId, debug);
|
|
2359
|
+
} else {
|
|
2360
|
+
rawSteps = JSON.parse(requestPayload);
|
|
2167
2361
|
}
|
|
2168
|
-
const { rawInitialPayload, steps } =
|
|
2362
|
+
const { rawInitialPayload, steps } = processRawSteps(rawSteps);
|
|
2169
2363
|
const isLastDuplicate = await checkIfLastOneIsDuplicate(steps, debug);
|
|
2170
2364
|
const deduplicatedSteps = deduplicateSteps(steps);
|
|
2171
2365
|
return {
|
|
@@ -2175,13 +2369,13 @@ var parseRequest = async (requestPayload, isFirstInvocation, debug) => {
|
|
|
2175
2369
|
};
|
|
2176
2370
|
}
|
|
2177
2371
|
};
|
|
2178
|
-
var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, failureFunction, debug) => {
|
|
2372
|
+
var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, debug) => {
|
|
2179
2373
|
if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
|
|
2180
2374
|
return ok("not-failure-callback");
|
|
2181
2375
|
}
|
|
2182
2376
|
if (!failureFunction) {
|
|
2183
2377
|
return err(
|
|
2184
|
-
new
|
|
2378
|
+
new WorkflowError(
|
|
2185
2379
|
"Workflow endpoint is called to handle a failure, but a failureFunction is not provided in serve options. Either provide a failureUrl or a failureFunction."
|
|
2186
2380
|
)
|
|
2187
2381
|
);
|
|
@@ -2192,92 +2386,48 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
|
|
|
2192
2386
|
);
|
|
2193
2387
|
const decodedBody = body ? decodeBase64(body) : "{}";
|
|
2194
2388
|
const errorPayload = JSON.parse(decodedBody);
|
|
2195
|
-
const {
|
|
2196
|
-
rawInitialPayload,
|
|
2197
|
-
steps,
|
|
2198
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2199
|
-
isLastDuplicate: _isLastDuplicate
|
|
2200
|
-
} = await parseRequest(decodeBase64(sourceBody), false, debug);
|
|
2201
2389
|
const workflowContext = new WorkflowContext({
|
|
2202
2390
|
qstashClient,
|
|
2203
2391
|
workflowRunId,
|
|
2204
|
-
initialPayload: initialPayloadParser(
|
|
2205
|
-
rawInitialPayload,
|
|
2392
|
+
initialPayload: initialPayloadParser(decodeBase64(sourceBody)),
|
|
2206
2393
|
headers: recreateUserHeaders(new Headers(sourceHeader)),
|
|
2207
|
-
steps,
|
|
2394
|
+
steps: [],
|
|
2208
2395
|
url,
|
|
2209
2396
|
failureUrl: url,
|
|
2210
2397
|
debug
|
|
2211
2398
|
});
|
|
2212
|
-
|
|
2399
|
+
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
2400
|
+
routeFunction,
|
|
2401
|
+
workflowContext
|
|
2402
|
+
);
|
|
2403
|
+
if (authCheck.isErr()) {
|
|
2404
|
+
await debug?.log("ERROR", "ERROR", { error: authCheck.error.message });
|
|
2405
|
+
throw authCheck.error;
|
|
2406
|
+
} else if (authCheck.value === "run-ended") {
|
|
2407
|
+
return err(new WorkflowError("Not authorized to run the failure function."));
|
|
2408
|
+
}
|
|
2409
|
+
await failureFunction({
|
|
2410
|
+
context: workflowContext,
|
|
2411
|
+
failStatus: status,
|
|
2412
|
+
failResponse: errorPayload.message,
|
|
2413
|
+
failHeaders: header
|
|
2414
|
+
});
|
|
2213
2415
|
} catch (error) {
|
|
2214
2416
|
return err(error);
|
|
2215
2417
|
}
|
|
2216
2418
|
return ok("is-failure-callback");
|
|
2217
2419
|
};
|
|
2218
2420
|
|
|
2219
|
-
// src/serve/authorization.ts
|
|
2220
|
-
var import_qstash2 = require("@upstash/qstash");
|
|
2221
|
-
var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
|
|
2222
|
-
static disabledMessage = "disabled-qstash-worklfow-run";
|
|
2223
|
-
/**
|
|
2224
|
-
* overwrite the WorkflowContext.addStep method to always raise QStashWorkflowAbort
|
|
2225
|
-
* error in order to stop the execution whenever we encounter a step.
|
|
2226
|
-
*
|
|
2227
|
-
* @param _step
|
|
2228
|
-
*/
|
|
2229
|
-
async addStep(_step) {
|
|
2230
|
-
throw new QStashWorkflowAbort(_DisabledWorkflowContext.disabledMessage);
|
|
2231
|
-
}
|
|
2232
|
-
/**
|
|
2233
|
-
* copies the passed context to create a DisabledWorkflowContext. Then, runs the
|
|
2234
|
-
* route function with the new context.
|
|
2235
|
-
*
|
|
2236
|
-
* - returns "run-ended" if there are no steps found or
|
|
2237
|
-
* if the auth failed and user called `return`
|
|
2238
|
-
* - returns "step-found" if DisabledWorkflowContext.addStep is called.
|
|
2239
|
-
* - if there is another error, returns the error.
|
|
2240
|
-
*
|
|
2241
|
-
* @param routeFunction
|
|
2242
|
-
*/
|
|
2243
|
-
static async tryAuthentication(routeFunction, context) {
|
|
2244
|
-
const disabledContext = new _DisabledWorkflowContext({
|
|
2245
|
-
qstashClient: new import_qstash2.Client({
|
|
2246
|
-
baseUrl: "disabled-client",
|
|
2247
|
-
token: "disabled-client"
|
|
2248
|
-
}),
|
|
2249
|
-
workflowRunId: context.workflowRunId,
|
|
2250
|
-
headers: context.headers,
|
|
2251
|
-
steps: [],
|
|
2252
|
-
url: context.url,
|
|
2253
|
-
failureUrl: context.failureUrl,
|
|
2254
|
-
initialPayload: context.requestPayload,
|
|
2255
|
-
rawInitialPayload: context.rawInitialPayload,
|
|
2256
|
-
env: context.env,
|
|
2257
|
-
retries: context.retries
|
|
2258
|
-
});
|
|
2259
|
-
try {
|
|
2260
|
-
await routeFunction(disabledContext);
|
|
2261
|
-
} catch (error) {
|
|
2262
|
-
if (error instanceof QStashWorkflowAbort && error.stepName === this.disabledMessage) {
|
|
2263
|
-
return ok("step-found");
|
|
2264
|
-
}
|
|
2265
|
-
return err(error);
|
|
2266
|
-
}
|
|
2267
|
-
return ok("run-ended");
|
|
2268
|
-
}
|
|
2269
|
-
};
|
|
2270
|
-
|
|
2271
2421
|
// src/serve/options.ts
|
|
2272
|
-
var import_qstash3 = require("@upstash/qstash");
|
|
2273
2422
|
var import_qstash4 = require("@upstash/qstash");
|
|
2423
|
+
var import_qstash5 = require("@upstash/qstash");
|
|
2274
2424
|
var processOptions = (options) => {
|
|
2275
2425
|
const environment = options?.env ?? (typeof process === "undefined" ? {} : process.env);
|
|
2276
2426
|
const receiverEnvironmentVariablesSet = Boolean(
|
|
2277
2427
|
environment.QSTASH_CURRENT_SIGNING_KEY && environment.QSTASH_NEXT_SIGNING_KEY
|
|
2278
2428
|
);
|
|
2279
2429
|
return {
|
|
2280
|
-
qstashClient: new
|
|
2430
|
+
qstashClient: new import_qstash5.Client({
|
|
2281
2431
|
baseUrl: environment.QSTASH_URL,
|
|
2282
2432
|
token: environment.QSTASH_TOKEN
|
|
2283
2433
|
}),
|
|
@@ -2298,7 +2448,7 @@ var processOptions = (options) => {
|
|
|
2298
2448
|
throw error;
|
|
2299
2449
|
}
|
|
2300
2450
|
},
|
|
2301
|
-
receiver: receiverEnvironmentVariablesSet ? new
|
|
2451
|
+
receiver: receiverEnvironmentVariablesSet ? new import_qstash4.Receiver({
|
|
2302
2452
|
currentSigningKey: environment.QSTASH_CURRENT_SIGNING_KEY,
|
|
2303
2453
|
nextSigningKey: environment.QSTASH_NEXT_SIGNING_KEY
|
|
2304
2454
|
}) : void 0,
|
|
@@ -2360,6 +2510,9 @@ var serve = (routeFunction, options) => {
|
|
|
2360
2510
|
const { rawInitialPayload, steps, isLastDuplicate } = await parseRequest(
|
|
2361
2511
|
requestPayload,
|
|
2362
2512
|
isFirstInvocation,
|
|
2513
|
+
workflowRunId,
|
|
2514
|
+
qstashClient.http,
|
|
2515
|
+
request.headers.get("upstash-message-id"),
|
|
2363
2516
|
debug
|
|
2364
2517
|
);
|
|
2365
2518
|
if (isLastDuplicate) {
|
|
@@ -2370,6 +2523,7 @@ var serve = (routeFunction, options) => {
|
|
|
2370
2523
|
requestPayload,
|
|
2371
2524
|
qstashClient,
|
|
2372
2525
|
initialPayloadParser,
|
|
2526
|
+
routeFunction,
|
|
2373
2527
|
failureFunction
|
|
2374
2528
|
);
|
|
2375
2529
|
if (failureCheck.isErr()) {
|
|
@@ -2382,7 +2536,6 @@ var serve = (routeFunction, options) => {
|
|
|
2382
2536
|
qstashClient,
|
|
2383
2537
|
workflowRunId,
|
|
2384
2538
|
initialPayload: initialPayloadParser(rawInitialPayload),
|
|
2385
|
-
rawInitialPayload,
|
|
2386
2539
|
headers: recreateUserHeaders(request.headers),
|
|
2387
2540
|
steps,
|
|
2388
2541
|
url: workflowUrl,
|
|
@@ -2420,7 +2573,11 @@ var serve = (routeFunction, options) => {
|
|
|
2420
2573
|
onStep: async () => routeFunction(workflowContext),
|
|
2421
2574
|
onCleanup: async () => {
|
|
2422
2575
|
await triggerWorkflowDelete(workflowContext, debug);
|
|
2423
|
-
}
|
|
2576
|
+
},
|
|
2577
|
+
onCancel: async () => {
|
|
2578
|
+
await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
|
|
2579
|
+
},
|
|
2580
|
+
debug
|
|
2424
2581
|
});
|
|
2425
2582
|
if (result.isErr()) {
|
|
2426
2583
|
await debug?.log("ERROR", "ERROR", { error: result.error.message });
|
|
@@ -2446,7 +2603,7 @@ var serve = (routeFunction, options) => {
|
|
|
2446
2603
|
};
|
|
2447
2604
|
|
|
2448
2605
|
// src/client/index.ts
|
|
2449
|
-
var
|
|
2606
|
+
var import_qstash6 = require("@upstash/qstash");
|
|
2450
2607
|
|
|
2451
2608
|
// platforms/h3.ts
|
|
2452
2609
|
function transformHeaders(headers) {
|