@upstash/workflow 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/astro.d.mts +2 -2
- package/astro.d.ts +2 -2
- package/astro.js +28 -2
- package/astro.mjs +1 -1
- package/{chunk-C5HFGF7Q.mjs → chunk-QRCGBBFJ.mjs} +28 -2
- package/cloudflare.d.mts +2 -2
- package/cloudflare.d.ts +2 -2
- package/cloudflare.js +28 -2
- package/cloudflare.mjs +1 -1
- package/express.d.mts +2 -2
- package/express.d.ts +2 -2
- package/express.js +28 -2
- package/express.mjs +1 -1
- package/h3.d.mts +2 -2
- package/h3.d.ts +2 -2
- package/h3.js +32 -6
- package/h3.mjs +5 -5
- package/hono.d.mts +2 -2
- package/hono.d.ts +2 -2
- package/hono.js +28 -2
- package/hono.mjs +1 -1
- package/index.d.mts +3 -3
- package/index.d.ts +3 -3
- package/index.js +31 -3
- package/index.mjs +4 -2
- package/nextjs.d.mts +2 -2
- package/nextjs.d.ts +2 -2
- package/nextjs.js +28 -2
- package/nextjs.mjs +1 -1
- package/package.json +1 -1
- package/react-router.d.mts +38 -0
- package/react-router.d.ts +38 -0
- package/react-router.js +3861 -0
- package/react-router.mjs +45 -0
- package/{serve-many-qnfynN1x.d.ts → serve-many-BP-8Ytbc.d.ts} +1 -1
- package/{serve-many-DhB8-zPD.d.mts → serve-many-BsycEL_d.d.mts} +1 -1
- package/solidjs.d.mts +1 -1
- package/solidjs.d.ts +1 -1
- package/solidjs.js +28 -2
- package/solidjs.mjs +1 -1
- package/svelte.d.mts +2 -2
- package/svelte.d.ts +2 -2
- package/svelte.js +28 -2
- package/svelte.mjs +1 -1
- package/tanstack.d.mts +2 -2
- package/tanstack.d.ts +2 -2
- package/tanstack.js +28 -2
- package/tanstack.mjs +1 -1
- package/{types-pEje3VEB.d.ts → types-B90SJYZV.d.mts} +9 -2
- package/{types-pEje3VEB.d.mts → types-B90SJYZV.d.ts} +9 -2
package/react-router.js
ADDED
|
@@ -0,0 +1,3861 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// platforms/react-router.ts
|
|
21
|
+
var react_router_exports = {};
|
|
22
|
+
__export(react_router_exports, {
|
|
23
|
+
createWorkflow: () => createWorkflow,
|
|
24
|
+
serve: () => serve,
|
|
25
|
+
serveMany: () => serveMany
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(react_router_exports);
|
|
28
|
+
|
|
29
|
+
// src/constants.ts
|
|
30
|
+
var WORKFLOW_ID_HEADER = "Upstash-Workflow-RunId";
|
|
31
|
+
var WORKFLOW_INIT_HEADER = "Upstash-Workflow-Init";
|
|
32
|
+
var WORKFLOW_URL_HEADER = "Upstash-Workflow-Url";
|
|
33
|
+
var WORKFLOW_CREATED_AT_HEADER = "Upstash-Workflow-CreatedAt";
|
|
34
|
+
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
|
|
35
|
+
var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
|
|
36
|
+
var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
|
|
37
|
+
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
|
|
38
|
+
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
|
|
39
|
+
var WORKFLOW_UNKOWN_SDK_VERSION_HEADER = "Upstash-Workflow-Unknown-Sdk";
|
|
40
|
+
var WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER = "upstash-workflow-trigger-by-sdk";
|
|
41
|
+
var WORKFLOW_PROTOCOL_VERSION = "1";
|
|
42
|
+
var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
|
|
43
|
+
var DEFAULT_CONTENT_TYPE = "application/json";
|
|
44
|
+
var NO_CONCURRENCY = 1;
|
|
45
|
+
var DEFAULT_RETRIES = 3;
|
|
46
|
+
var VERSION = "v1.0.0";
|
|
47
|
+
var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
|
|
48
|
+
var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
|
|
49
|
+
var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
|
|
50
|
+
var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
|
|
51
|
+
|
|
52
|
+
// src/client/utils.ts
|
|
53
|
+
var import_qstash2 = require("@upstash/qstash");
|
|
54
|
+
|
|
55
|
+
// src/error.ts
|
|
56
|
+
var import_qstash = require("@upstash/qstash");
|
|
57
|
+
var WorkflowError = class extends import_qstash.QstashError {
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = "WorkflowError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var WorkflowAbort = class extends Error {
|
|
64
|
+
stepName;
|
|
65
|
+
stepInfo;
|
|
66
|
+
/**
|
|
67
|
+
* @param stepName name of the aborting step
|
|
68
|
+
* @param stepInfo step information
|
|
69
|
+
*/
|
|
70
|
+
constructor(stepName, stepInfo) {
|
|
71
|
+
super(
|
|
72
|
+
`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}'.`
|
|
73
|
+
);
|
|
74
|
+
this.name = "WorkflowAbort";
|
|
75
|
+
this.stepName = stepName;
|
|
76
|
+
this.stepInfo = stepInfo;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var WorkflowAuthError = class extends WorkflowAbort {
|
|
80
|
+
/**
|
|
81
|
+
* @param stepName name of the step found during authorization
|
|
82
|
+
*/
|
|
83
|
+
constructor(stepName) {
|
|
84
|
+
super(stepName);
|
|
85
|
+
this.name = "WorkflowAuthError";
|
|
86
|
+
this.message = `This is an Upstash Workflow error thrown during authorization check. Found step '${stepName}' during dry-run.`;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var WorkflowCancelAbort = class extends WorkflowAbort {
|
|
90
|
+
constructor() {
|
|
91
|
+
super("cancel");
|
|
92
|
+
this.name = "WorkflowCancelAbort";
|
|
93
|
+
this.message = "Workflow has been canceled by user via context.cancel().";
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var WorkflowNonRetryableError = class extends WorkflowAbort {
|
|
97
|
+
/**
|
|
98
|
+
* @param message error message to be displayed
|
|
99
|
+
*/
|
|
100
|
+
constructor(message) {
|
|
101
|
+
super("non-retryable-error");
|
|
102
|
+
this.name = "WorkflowNonRetryableError";
|
|
103
|
+
this.message = message ?? "Workflow failed with non-retryable error.";
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var WorkflowRetryAfterError = class extends WorkflowAbort {
|
|
107
|
+
retryAfter;
|
|
108
|
+
/**
|
|
109
|
+
* @param message error message to be displayed
|
|
110
|
+
* @param retryAfter time in seconds after which the workflow should be retried
|
|
111
|
+
*/
|
|
112
|
+
constructor(message, retryAfter) {
|
|
113
|
+
super("retry-after-error");
|
|
114
|
+
this.name = "WorkflowRetryAfterError";
|
|
115
|
+
this.message = message;
|
|
116
|
+
this.retryAfter = retryAfter;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var formatWorkflowError = (error) => {
|
|
120
|
+
return error instanceof Error ? {
|
|
121
|
+
error: error.name,
|
|
122
|
+
message: error.message,
|
|
123
|
+
stack: error.stack
|
|
124
|
+
} : {
|
|
125
|
+
error: "Error",
|
|
126
|
+
message: `An error occured while executing workflow: '${typeof error === "string" ? error : JSON.stringify(error)}'`
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
function getConstructorName(obj) {
|
|
130
|
+
if (obj === null || obj === void 0) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const ctor = obj.constructor;
|
|
134
|
+
if (!ctor || ctor.name === "Object") {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return ctor.name;
|
|
138
|
+
}
|
|
139
|
+
function getConstructorNames(obj) {
|
|
140
|
+
const proto = Object.getPrototypeOf(obj);
|
|
141
|
+
const name = getConstructorName(proto);
|
|
142
|
+
if (name === null) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
return [name, ...getConstructorNames(proto)];
|
|
146
|
+
}
|
|
147
|
+
function isInstanceOf(v, ctor) {
|
|
148
|
+
return getConstructorNames(v).includes(ctor.name);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/client/utils.ts
|
|
152
|
+
var makeNotifyRequest = async (requester, eventId, eventData) => {
|
|
153
|
+
const result = await requester.request({
|
|
154
|
+
path: ["v2", "notify", eventId],
|
|
155
|
+
method: "POST",
|
|
156
|
+
body: typeof eventData === "string" ? eventData : JSON.stringify(eventData)
|
|
157
|
+
});
|
|
158
|
+
return result;
|
|
159
|
+
};
|
|
160
|
+
var makeCancelRequest = async (requester, workflowRunId) => {
|
|
161
|
+
await requester.request({
|
|
162
|
+
path: ["v2", "workflows", "runs", `${workflowRunId}?cancel=true`],
|
|
163
|
+
method: "DELETE",
|
|
164
|
+
parseResponseAsJson: false
|
|
165
|
+
});
|
|
166
|
+
return true;
|
|
167
|
+
};
|
|
168
|
+
var getSteps = async (requester, workflowRunId, messageId, dispatchDebug) => {
|
|
169
|
+
try {
|
|
170
|
+
const steps = await requester.request({
|
|
171
|
+
path: ["v2", "workflows", "runs", workflowRunId],
|
|
172
|
+
parseResponseAsJson: true
|
|
173
|
+
});
|
|
174
|
+
if (steps.length === 1) {
|
|
175
|
+
return {
|
|
176
|
+
steps,
|
|
177
|
+
workflowRunEnded: false
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (!messageId) {
|
|
181
|
+
await dispatchDebug?.("onInfo", {
|
|
182
|
+
info: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
|
|
183
|
+
});
|
|
184
|
+
return { steps, workflowRunEnded: false };
|
|
185
|
+
} else {
|
|
186
|
+
const index = steps.findIndex((item) => item.messageId === messageId);
|
|
187
|
+
if (index === -1) {
|
|
188
|
+
return { steps: [], workflowRunEnded: false };
|
|
189
|
+
}
|
|
190
|
+
const filteredSteps = steps.slice(0, index + 1);
|
|
191
|
+
await dispatchDebug?.("onInfo", {
|
|
192
|
+
info: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
|
|
193
|
+
});
|
|
194
|
+
return { steps: filteredSteps, workflowRunEnded: false };
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (isInstanceOf(error, import_qstash2.QstashError) && error.status === 404) {
|
|
198
|
+
await dispatchDebug?.("onWarning", {
|
|
199
|
+
warning: "Couldn't fetch workflow run steps. This can happen if the workflow run succesfully ends before some callback is executed."
|
|
200
|
+
});
|
|
201
|
+
return { steps: void 0, workflowRunEnded: true };
|
|
202
|
+
} else {
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// src/context/auto-executor.ts
|
|
209
|
+
var import_qstash5 = require("@upstash/qstash");
|
|
210
|
+
|
|
211
|
+
// src/qstash/headers.ts
|
|
212
|
+
var import_qstash4 = require("@upstash/qstash");
|
|
213
|
+
|
|
214
|
+
// src/utils.ts
|
|
215
|
+
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
216
|
+
var NANOID_LENGTH = 21;
|
|
217
|
+
function getRandomInt() {
|
|
218
|
+
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
219
|
+
}
|
|
220
|
+
function nanoid(length = NANOID_LENGTH) {
|
|
221
|
+
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
222
|
+
}
|
|
223
|
+
function getWorkflowRunId(id) {
|
|
224
|
+
return `wfr_${id ?? nanoid()}`;
|
|
225
|
+
}
|
|
226
|
+
function decodeBase64(base64) {
|
|
227
|
+
const binString = atob(base64);
|
|
228
|
+
try {
|
|
229
|
+
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
230
|
+
return new TextDecoder().decode(intArray);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.warn(
|
|
233
|
+
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
234
|
+
);
|
|
235
|
+
return binString;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function getUserIdFromToken(qstashClient) {
|
|
239
|
+
try {
|
|
240
|
+
const token = qstashClient.token;
|
|
241
|
+
const decodedToken = decodeBase64(token);
|
|
242
|
+
const tokenPayload = JSON.parse(decodedToken);
|
|
243
|
+
const userId = tokenPayload.UserID;
|
|
244
|
+
if (!userId) {
|
|
245
|
+
throw new WorkflowError("QStash token payload does not contain userId");
|
|
246
|
+
}
|
|
247
|
+
return userId;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
throw new WorkflowError(
|
|
250
|
+
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function getQStashUrl(qstashClient) {
|
|
255
|
+
try {
|
|
256
|
+
const requester = qstashClient.http;
|
|
257
|
+
const baseUrl = requester.baseUrl;
|
|
258
|
+
if (!baseUrl) {
|
|
259
|
+
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
260
|
+
}
|
|
261
|
+
return baseUrl;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function getEventId() {
|
|
267
|
+
return `evt_${nanoid(15)}`;
|
|
268
|
+
}
|
|
269
|
+
function stringifyBody(body) {
|
|
270
|
+
if (body === void 0) {
|
|
271
|
+
return void 0;
|
|
272
|
+
}
|
|
273
|
+
if (typeof body === "string") {
|
|
274
|
+
return body;
|
|
275
|
+
}
|
|
276
|
+
return JSON.stringify(body);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// node_modules/neverthrow/dist/index.es.js
|
|
280
|
+
var defaultErrorConfig = {
|
|
281
|
+
withStackTrace: false
|
|
282
|
+
};
|
|
283
|
+
var createNeverThrowError = (message, result, config = defaultErrorConfig) => {
|
|
284
|
+
const data = result.isOk() ? { type: "Ok", value: result.value } : { type: "Err", value: result.error };
|
|
285
|
+
const maybeStack = config.withStackTrace ? new Error().stack : void 0;
|
|
286
|
+
return {
|
|
287
|
+
data,
|
|
288
|
+
message,
|
|
289
|
+
stack: maybeStack
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
293
|
+
function adopt(value) {
|
|
294
|
+
return value instanceof P ? value : new P(function(resolve) {
|
|
295
|
+
resolve(value);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return new (P || (P = Promise))(function(resolve, reject) {
|
|
299
|
+
function fulfilled(value) {
|
|
300
|
+
try {
|
|
301
|
+
step(generator.next(value));
|
|
302
|
+
} catch (e) {
|
|
303
|
+
reject(e);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function rejected(value) {
|
|
307
|
+
try {
|
|
308
|
+
step(generator["throw"](value));
|
|
309
|
+
} catch (e) {
|
|
310
|
+
reject(e);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function step(result) {
|
|
314
|
+
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
315
|
+
}
|
|
316
|
+
step((generator = generator.apply(thisArg, [])).next());
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
function __values(o) {
|
|
320
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
321
|
+
if (m) return m.call(o);
|
|
322
|
+
if (o && typeof o.length === "number") return {
|
|
323
|
+
next: function() {
|
|
324
|
+
if (o && i >= o.length) o = void 0;
|
|
325
|
+
return { value: o && o[i++], done: !o };
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
329
|
+
}
|
|
330
|
+
function __await(v) {
|
|
331
|
+
return this instanceof __await ? (this.v = v, this) : new __await(v);
|
|
332
|
+
}
|
|
333
|
+
function __asyncGenerator(thisArg, _arguments, generator) {
|
|
334
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
335
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
336
|
+
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function() {
|
|
337
|
+
return this;
|
|
338
|
+
}, i;
|
|
339
|
+
function verb(n) {
|
|
340
|
+
if (g[n]) i[n] = function(v) {
|
|
341
|
+
return new Promise(function(a, b) {
|
|
342
|
+
q.push([n, v, a, b]) > 1 || resume(n, v);
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function resume(n, v) {
|
|
347
|
+
try {
|
|
348
|
+
step(g[n](v));
|
|
349
|
+
} catch (e) {
|
|
350
|
+
settle(q[0][3], e);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function step(r) {
|
|
354
|
+
r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r);
|
|
355
|
+
}
|
|
356
|
+
function fulfill(value) {
|
|
357
|
+
resume("next", value);
|
|
358
|
+
}
|
|
359
|
+
function reject(value) {
|
|
360
|
+
resume("throw", value);
|
|
361
|
+
}
|
|
362
|
+
function settle(f, v) {
|
|
363
|
+
if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function __asyncDelegator(o) {
|
|
367
|
+
var i, p;
|
|
368
|
+
return i = {}, verb("next"), verb("throw", function(e) {
|
|
369
|
+
throw e;
|
|
370
|
+
}), verb("return"), i[Symbol.iterator] = function() {
|
|
371
|
+
return this;
|
|
372
|
+
}, i;
|
|
373
|
+
function verb(n, f) {
|
|
374
|
+
i[n] = o[n] ? function(v) {
|
|
375
|
+
return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v;
|
|
376
|
+
} : f;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function __asyncValues(o) {
|
|
380
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
381
|
+
var m = o[Symbol.asyncIterator], i;
|
|
382
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function() {
|
|
383
|
+
return this;
|
|
384
|
+
}, i);
|
|
385
|
+
function verb(n) {
|
|
386
|
+
i[n] = o[n] && function(v) {
|
|
387
|
+
return new Promise(function(resolve, reject) {
|
|
388
|
+
v = o[n](v), settle(resolve, reject, v.done, v.value);
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function settle(resolve, reject, d, v) {
|
|
393
|
+
Promise.resolve(v).then(function(v2) {
|
|
394
|
+
resolve({ value: v2, done: d });
|
|
395
|
+
}, reject);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
var ResultAsync = class _ResultAsync {
|
|
399
|
+
constructor(res) {
|
|
400
|
+
this._promise = res;
|
|
401
|
+
}
|
|
402
|
+
static fromSafePromise(promise) {
|
|
403
|
+
const newPromise = promise.then((value) => new Ok(value));
|
|
404
|
+
return new _ResultAsync(newPromise);
|
|
405
|
+
}
|
|
406
|
+
static fromPromise(promise, errorFn) {
|
|
407
|
+
const newPromise = promise.then((value) => new Ok(value)).catch((e) => new Err(errorFn(e)));
|
|
408
|
+
return new _ResultAsync(newPromise);
|
|
409
|
+
}
|
|
410
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
411
|
+
static fromThrowable(fn, errorFn) {
|
|
412
|
+
return (...args) => {
|
|
413
|
+
return new _ResultAsync((() => __awaiter(this, void 0, void 0, function* () {
|
|
414
|
+
try {
|
|
415
|
+
return new Ok(yield fn(...args));
|
|
416
|
+
} catch (error) {
|
|
417
|
+
return new Err(errorFn ? errorFn(error) : error);
|
|
418
|
+
}
|
|
419
|
+
}))());
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
static combine(asyncResultList) {
|
|
423
|
+
return combineResultAsyncList(asyncResultList);
|
|
424
|
+
}
|
|
425
|
+
static combineWithAllErrors(asyncResultList) {
|
|
426
|
+
return combineResultAsyncListWithAllErrors(asyncResultList);
|
|
427
|
+
}
|
|
428
|
+
map(f) {
|
|
429
|
+
return new _ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
|
|
430
|
+
if (res.isErr()) {
|
|
431
|
+
return new Err(res.error);
|
|
432
|
+
}
|
|
433
|
+
return new Ok(yield f(res.value));
|
|
434
|
+
})));
|
|
435
|
+
}
|
|
436
|
+
andThrough(f) {
|
|
437
|
+
return new _ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
|
|
438
|
+
if (res.isErr()) {
|
|
439
|
+
return new Err(res.error);
|
|
440
|
+
}
|
|
441
|
+
const newRes = yield f(res.value);
|
|
442
|
+
if (newRes.isErr()) {
|
|
443
|
+
return new Err(newRes.error);
|
|
444
|
+
}
|
|
445
|
+
return new Ok(res.value);
|
|
446
|
+
})));
|
|
447
|
+
}
|
|
448
|
+
andTee(f) {
|
|
449
|
+
return new _ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
|
|
450
|
+
if (res.isErr()) {
|
|
451
|
+
return new Err(res.error);
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
yield f(res.value);
|
|
455
|
+
} catch (e) {
|
|
456
|
+
}
|
|
457
|
+
return new Ok(res.value);
|
|
458
|
+
})));
|
|
459
|
+
}
|
|
460
|
+
mapErr(f) {
|
|
461
|
+
return new _ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
|
|
462
|
+
if (res.isOk()) {
|
|
463
|
+
return new Ok(res.value);
|
|
464
|
+
}
|
|
465
|
+
return new Err(yield f(res.error));
|
|
466
|
+
})));
|
|
467
|
+
}
|
|
468
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
469
|
+
andThen(f) {
|
|
470
|
+
return new _ResultAsync(this._promise.then((res) => {
|
|
471
|
+
if (res.isErr()) {
|
|
472
|
+
return new Err(res.error);
|
|
473
|
+
}
|
|
474
|
+
const newValue = f(res.value);
|
|
475
|
+
return newValue instanceof _ResultAsync ? newValue._promise : newValue;
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
478
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
479
|
+
orElse(f) {
|
|
480
|
+
return new _ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
|
|
481
|
+
if (res.isErr()) {
|
|
482
|
+
return f(res.error);
|
|
483
|
+
}
|
|
484
|
+
return new Ok(res.value);
|
|
485
|
+
})));
|
|
486
|
+
}
|
|
487
|
+
match(ok2, _err) {
|
|
488
|
+
return this._promise.then((res) => res.match(ok2, _err));
|
|
489
|
+
}
|
|
490
|
+
unwrapOr(t) {
|
|
491
|
+
return this._promise.then((res) => res.unwrapOr(t));
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Emulates Rust's `?` operator in `safeTry`'s body. See also `safeTry`.
|
|
495
|
+
*/
|
|
496
|
+
safeUnwrap() {
|
|
497
|
+
return __asyncGenerator(this, arguments, function* safeUnwrap_1() {
|
|
498
|
+
return yield __await(yield __await(yield* __asyncDelegator(__asyncValues(yield __await(this._promise.then((res) => res.safeUnwrap()))))));
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
// Makes ResultAsync implement PromiseLike<Result>
|
|
502
|
+
then(successCallback, failureCallback) {
|
|
503
|
+
return this._promise.then(successCallback, failureCallback);
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
var errAsync = (err2) => new ResultAsync(Promise.resolve(new Err(err2)));
|
|
507
|
+
var fromPromise = ResultAsync.fromPromise;
|
|
508
|
+
var fromSafePromise = ResultAsync.fromSafePromise;
|
|
509
|
+
var fromAsyncThrowable = ResultAsync.fromThrowable;
|
|
510
|
+
var combineResultList = (resultList) => {
|
|
511
|
+
let acc = ok([]);
|
|
512
|
+
for (const result of resultList) {
|
|
513
|
+
if (result.isErr()) {
|
|
514
|
+
acc = err(result.error);
|
|
515
|
+
break;
|
|
516
|
+
} else {
|
|
517
|
+
acc.map((list) => list.push(result.value));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return acc;
|
|
521
|
+
};
|
|
522
|
+
var combineResultAsyncList = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultList);
|
|
523
|
+
var combineResultListWithAllErrors = (resultList) => {
|
|
524
|
+
let acc = ok([]);
|
|
525
|
+
for (const result of resultList) {
|
|
526
|
+
if (result.isErr() && acc.isErr()) {
|
|
527
|
+
acc.error.push(result.error);
|
|
528
|
+
} else if (result.isErr() && acc.isOk()) {
|
|
529
|
+
acc = err([result.error]);
|
|
530
|
+
} else if (result.isOk() && acc.isOk()) {
|
|
531
|
+
acc.value.push(result.value);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return acc;
|
|
535
|
+
};
|
|
536
|
+
var combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);
|
|
537
|
+
var Result;
|
|
538
|
+
(function(Result2) {
|
|
539
|
+
function fromThrowable2(fn, errorFn) {
|
|
540
|
+
return (...args) => {
|
|
541
|
+
try {
|
|
542
|
+
const result = fn(...args);
|
|
543
|
+
return ok(result);
|
|
544
|
+
} catch (e) {
|
|
545
|
+
return err(errorFn ? errorFn(e) : e);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
Result2.fromThrowable = fromThrowable2;
|
|
550
|
+
function combine(resultList) {
|
|
551
|
+
return combineResultList(resultList);
|
|
552
|
+
}
|
|
553
|
+
Result2.combine = combine;
|
|
554
|
+
function combineWithAllErrors(resultList) {
|
|
555
|
+
return combineResultListWithAllErrors(resultList);
|
|
556
|
+
}
|
|
557
|
+
Result2.combineWithAllErrors = combineWithAllErrors;
|
|
558
|
+
})(Result || (Result = {}));
|
|
559
|
+
var ok = (value) => new Ok(value);
|
|
560
|
+
function err(err2) {
|
|
561
|
+
return new Err(err2);
|
|
562
|
+
}
|
|
563
|
+
var Ok = class {
|
|
564
|
+
constructor(value) {
|
|
565
|
+
this.value = value;
|
|
566
|
+
}
|
|
567
|
+
isOk() {
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
isErr() {
|
|
571
|
+
return !this.isOk();
|
|
572
|
+
}
|
|
573
|
+
map(f) {
|
|
574
|
+
return ok(f(this.value));
|
|
575
|
+
}
|
|
576
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
577
|
+
mapErr(_f) {
|
|
578
|
+
return ok(this.value);
|
|
579
|
+
}
|
|
580
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
581
|
+
andThen(f) {
|
|
582
|
+
return f(this.value);
|
|
583
|
+
}
|
|
584
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
585
|
+
andThrough(f) {
|
|
586
|
+
return f(this.value).map((_value) => this.value);
|
|
587
|
+
}
|
|
588
|
+
andTee(f) {
|
|
589
|
+
try {
|
|
590
|
+
f(this.value);
|
|
591
|
+
} catch (e) {
|
|
592
|
+
}
|
|
593
|
+
return ok(this.value);
|
|
594
|
+
}
|
|
595
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
596
|
+
orElse(_f) {
|
|
597
|
+
return ok(this.value);
|
|
598
|
+
}
|
|
599
|
+
asyncAndThen(f) {
|
|
600
|
+
return f(this.value);
|
|
601
|
+
}
|
|
602
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
603
|
+
asyncAndThrough(f) {
|
|
604
|
+
return f(this.value).map(() => this.value);
|
|
605
|
+
}
|
|
606
|
+
asyncMap(f) {
|
|
607
|
+
return ResultAsync.fromSafePromise(f(this.value));
|
|
608
|
+
}
|
|
609
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
610
|
+
unwrapOr(_v) {
|
|
611
|
+
return this.value;
|
|
612
|
+
}
|
|
613
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
614
|
+
match(ok2, _err) {
|
|
615
|
+
return ok2(this.value);
|
|
616
|
+
}
|
|
617
|
+
safeUnwrap() {
|
|
618
|
+
const value = this.value;
|
|
619
|
+
return (function* () {
|
|
620
|
+
return value;
|
|
621
|
+
})();
|
|
622
|
+
}
|
|
623
|
+
_unsafeUnwrap(_) {
|
|
624
|
+
return this.value;
|
|
625
|
+
}
|
|
626
|
+
_unsafeUnwrapErr(config) {
|
|
627
|
+
throw createNeverThrowError("Called `_unsafeUnwrapErr` on an Ok", this, config);
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
var Err = class {
|
|
631
|
+
constructor(error) {
|
|
632
|
+
this.error = error;
|
|
633
|
+
}
|
|
634
|
+
isOk() {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
isErr() {
|
|
638
|
+
return !this.isOk();
|
|
639
|
+
}
|
|
640
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
641
|
+
map(_f) {
|
|
642
|
+
return err(this.error);
|
|
643
|
+
}
|
|
644
|
+
mapErr(f) {
|
|
645
|
+
return err(f(this.error));
|
|
646
|
+
}
|
|
647
|
+
andThrough(_f) {
|
|
648
|
+
return err(this.error);
|
|
649
|
+
}
|
|
650
|
+
andTee(_f) {
|
|
651
|
+
return err(this.error);
|
|
652
|
+
}
|
|
653
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
654
|
+
andThen(_f) {
|
|
655
|
+
return err(this.error);
|
|
656
|
+
}
|
|
657
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
658
|
+
orElse(f) {
|
|
659
|
+
return f(this.error);
|
|
660
|
+
}
|
|
661
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
662
|
+
asyncAndThen(_f) {
|
|
663
|
+
return errAsync(this.error);
|
|
664
|
+
}
|
|
665
|
+
asyncAndThrough(_f) {
|
|
666
|
+
return errAsync(this.error);
|
|
667
|
+
}
|
|
668
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
669
|
+
asyncMap(_f) {
|
|
670
|
+
return errAsync(this.error);
|
|
671
|
+
}
|
|
672
|
+
unwrapOr(v) {
|
|
673
|
+
return v;
|
|
674
|
+
}
|
|
675
|
+
match(_ok, err2) {
|
|
676
|
+
return err2(this.error);
|
|
677
|
+
}
|
|
678
|
+
safeUnwrap() {
|
|
679
|
+
const error = this.error;
|
|
680
|
+
return (function* () {
|
|
681
|
+
yield err(error);
|
|
682
|
+
throw new Error("Do not use this generator out of `safeTry`");
|
|
683
|
+
})();
|
|
684
|
+
}
|
|
685
|
+
_unsafeUnwrap(config) {
|
|
686
|
+
throw createNeverThrowError("Called `_unsafeUnwrap` on an Err", this, config);
|
|
687
|
+
}
|
|
688
|
+
_unsafeUnwrapErr(_) {
|
|
689
|
+
return this.error;
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
var fromThrowable = Result.fromThrowable;
|
|
693
|
+
|
|
694
|
+
// src/types.ts
|
|
695
|
+
var StepTypes = [
|
|
696
|
+
"Initial",
|
|
697
|
+
"Run",
|
|
698
|
+
"SleepFor",
|
|
699
|
+
"SleepUntil",
|
|
700
|
+
"Call",
|
|
701
|
+
"Wait",
|
|
702
|
+
"Notify",
|
|
703
|
+
"Invoke",
|
|
704
|
+
"CreateWebhook",
|
|
705
|
+
"WaitForWebhook"
|
|
706
|
+
];
|
|
707
|
+
|
|
708
|
+
// src/workflow-requests.ts
|
|
709
|
+
var import_qstash3 = require("@upstash/qstash");
|
|
710
|
+
var triggerFirstInvocation = async (params) => {
|
|
711
|
+
const firstInvocationParams = Array.isArray(params) ? params : [params];
|
|
712
|
+
const workflowContextClient = firstInvocationParams[0].workflowContext.qstashClient;
|
|
713
|
+
const invocationBatch = firstInvocationParams.map(
|
|
714
|
+
({
|
|
715
|
+
workflowContext,
|
|
716
|
+
useJSONContent,
|
|
717
|
+
telemetry: telemetry2,
|
|
718
|
+
invokeCount,
|
|
719
|
+
delay,
|
|
720
|
+
notBefore,
|
|
721
|
+
failureUrl,
|
|
722
|
+
retries,
|
|
723
|
+
retryDelay,
|
|
724
|
+
flowControl,
|
|
725
|
+
unknownSdk
|
|
726
|
+
}) => {
|
|
727
|
+
const { headers } = getHeaders({
|
|
728
|
+
initHeaderValue: "true",
|
|
729
|
+
workflowConfig: {
|
|
730
|
+
workflowRunId: workflowContext.workflowRunId,
|
|
731
|
+
workflowUrl: workflowContext.url,
|
|
732
|
+
failureUrl,
|
|
733
|
+
retries,
|
|
734
|
+
retryDelay,
|
|
735
|
+
telemetry: telemetry2,
|
|
736
|
+
flowControl,
|
|
737
|
+
useJSONContent: useJSONContent ?? false
|
|
738
|
+
},
|
|
739
|
+
invokeCount: invokeCount ?? 0,
|
|
740
|
+
userHeaders: workflowContext.headers
|
|
741
|
+
});
|
|
742
|
+
if (workflowContext.headers.get("content-type")) {
|
|
743
|
+
headers["content-type"] = workflowContext.headers.get("content-type");
|
|
744
|
+
}
|
|
745
|
+
if (useJSONContent) {
|
|
746
|
+
headers["content-type"] = "application/json";
|
|
747
|
+
}
|
|
748
|
+
if (unknownSdk) {
|
|
749
|
+
headers[WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER] = "true";
|
|
750
|
+
}
|
|
751
|
+
if (workflowContext.label) {
|
|
752
|
+
headers[WORKFLOW_LABEL_HEADER] = workflowContext.label;
|
|
753
|
+
}
|
|
754
|
+
const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
|
|
755
|
+
return {
|
|
756
|
+
headers,
|
|
757
|
+
method: "POST",
|
|
758
|
+
body,
|
|
759
|
+
url: workflowContext.url,
|
|
760
|
+
delay,
|
|
761
|
+
notBefore
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
);
|
|
765
|
+
try {
|
|
766
|
+
const results = await workflowContextClient.batch(invocationBatch);
|
|
767
|
+
const invocationStatuses = [];
|
|
768
|
+
for (let i = 0; i < results.length; i++) {
|
|
769
|
+
const result = results[i];
|
|
770
|
+
const invocationParams = firstInvocationParams[i];
|
|
771
|
+
invocationParams.middlewareManager?.assignContext(invocationParams.workflowContext);
|
|
772
|
+
if (result.deduplicated) {
|
|
773
|
+
await invocationParams.middlewareManager?.dispatchDebug("onWarning", {
|
|
774
|
+
warning: `Workflow run ${invocationParams.workflowContext.workflowRunId} already exists. A new one isn't created.`
|
|
775
|
+
});
|
|
776
|
+
invocationStatuses.push("workflow-run-already-exists");
|
|
777
|
+
} else {
|
|
778
|
+
await invocationParams.middlewareManager?.dispatchDebug("onInfo", {
|
|
779
|
+
info: `Workflow run started successfully with URL ${invocationParams.workflowContext.url}.`
|
|
780
|
+
});
|
|
781
|
+
invocationStatuses.push("success");
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
const hasAnyDeduplicated = invocationStatuses.some(
|
|
785
|
+
(status) => status === "workflow-run-already-exists"
|
|
786
|
+
);
|
|
787
|
+
if (hasAnyDeduplicated) {
|
|
788
|
+
return ok("workflow-run-already-exists");
|
|
789
|
+
} else {
|
|
790
|
+
return ok("success");
|
|
791
|
+
}
|
|
792
|
+
} catch (error) {
|
|
793
|
+
const error_ = error;
|
|
794
|
+
return err(error_);
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
var triggerRouteFunction = async ({
|
|
798
|
+
onCleanup,
|
|
799
|
+
onStep,
|
|
800
|
+
onCancel,
|
|
801
|
+
middlewareManager
|
|
802
|
+
}) => {
|
|
803
|
+
try {
|
|
804
|
+
const result = await onStep();
|
|
805
|
+
await onCleanup(result);
|
|
806
|
+
return ok("workflow-finished");
|
|
807
|
+
} catch (error) {
|
|
808
|
+
const error_ = error;
|
|
809
|
+
if (isInstanceOf(error, import_qstash3.QstashError) && error.status === 400) {
|
|
810
|
+
await middlewareManager?.dispatchDebug("onWarning", {
|
|
811
|
+
warning: `Tried to append to a cancelled workflow. Exiting without publishing. Error: ${error.message}`
|
|
812
|
+
});
|
|
813
|
+
return ok("workflow-was-finished");
|
|
814
|
+
} else if (isInstanceOf(error_, WorkflowNonRetryableError) || isInstanceOf(error_, WorkflowRetryAfterError)) {
|
|
815
|
+
return ok(error_);
|
|
816
|
+
} else if (isInstanceOf(error_, WorkflowCancelAbort)) {
|
|
817
|
+
await onCancel();
|
|
818
|
+
return ok("workflow-finished");
|
|
819
|
+
} else if (isInstanceOf(error_, WorkflowAbort)) {
|
|
820
|
+
return ok("step-finished");
|
|
821
|
+
} else {
|
|
822
|
+
return err(error_);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
var triggerWorkflowDelete = async (workflowContext, result, cancel = false, dispatchDebug) => {
|
|
827
|
+
await dispatchDebug?.("onInfo", {
|
|
828
|
+
info: `Deleting workflow run ${workflowContext.workflowRunId} from QStash` + (cancel ? " with cancel=true." : ".")
|
|
829
|
+
});
|
|
830
|
+
await workflowContext.qstashClient.http.request({
|
|
831
|
+
path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
|
|
832
|
+
method: "DELETE",
|
|
833
|
+
parseResponseAsJson: false,
|
|
834
|
+
body: JSON.stringify(result)
|
|
835
|
+
});
|
|
836
|
+
await dispatchDebug?.("onInfo", {
|
|
837
|
+
info: `Workflow run ${workflowContext.workflowRunId} deleted from QStash successfully.`
|
|
838
|
+
});
|
|
839
|
+
};
|
|
840
|
+
var recreateUserHeaders = (headers) => {
|
|
841
|
+
const filteredHeaders = new Headers();
|
|
842
|
+
const pairs = headers.entries();
|
|
843
|
+
for (const [header, value] of pairs) {
|
|
844
|
+
const headerLowerCase = header.toLowerCase();
|
|
845
|
+
const isUserHeader = headerLowerCase !== "upstash-region" && !headerLowerCase.startsWith("upstash-workflow-") && // https://vercel.com/docs/edge-network/headers/request-headers#x-vercel-id
|
|
846
|
+
!headerLowerCase.startsWith("x-vercel-") && !headerLowerCase.startsWith("x-forwarded-") && // https://blog.cloudflare.com/preventing-request-loops-using-cdn-loop/
|
|
847
|
+
headerLowerCase !== "cf-connecting-ip" && headerLowerCase !== "cdn-loop" && headerLowerCase !== "cf-ew-via" && headerLowerCase !== "cf-ray" && // For Render https://render.com
|
|
848
|
+
headerLowerCase !== "render-proxy-ttl" || headerLowerCase === WORKFLOW_LABEL_HEADER.toLocaleLowerCase();
|
|
849
|
+
if (isUserHeader) {
|
|
850
|
+
filteredHeaders.append(header, value);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return filteredHeaders;
|
|
854
|
+
};
|
|
855
|
+
var handleThirdPartyCallResult = async ({
|
|
856
|
+
request,
|
|
857
|
+
requestPayload,
|
|
858
|
+
client,
|
|
859
|
+
workflowUrl,
|
|
860
|
+
telemetry: telemetry2,
|
|
861
|
+
middlewareManager
|
|
862
|
+
}) => {
|
|
863
|
+
try {
|
|
864
|
+
if (request.headers.get("Upstash-Workflow-Callback")) {
|
|
865
|
+
let callbackPayload;
|
|
866
|
+
if (requestPayload) {
|
|
867
|
+
callbackPayload = requestPayload;
|
|
868
|
+
} else {
|
|
869
|
+
const workflowRunId2 = request.headers.get("upstash-workflow-runid");
|
|
870
|
+
const messageId = request.headers.get("upstash-message-id");
|
|
871
|
+
if (!workflowRunId2)
|
|
872
|
+
throw new WorkflowError("workflow run id missing in context.call lazy fetch.");
|
|
873
|
+
if (!messageId) throw new WorkflowError("message id missing in context.call lazy fetch.");
|
|
874
|
+
const { steps, workflowRunEnded } = await getSteps(
|
|
875
|
+
client.http,
|
|
876
|
+
workflowRunId2,
|
|
877
|
+
messageId,
|
|
878
|
+
middlewareManager?.dispatchDebug.bind(middlewareManager)
|
|
879
|
+
);
|
|
880
|
+
if (workflowRunEnded) {
|
|
881
|
+
return ok("workflow-ended");
|
|
882
|
+
}
|
|
883
|
+
const failingStep = steps.find((step) => step.messageId === messageId);
|
|
884
|
+
if (!failingStep)
|
|
885
|
+
throw new WorkflowError(
|
|
886
|
+
"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.`)
|
|
887
|
+
);
|
|
888
|
+
callbackPayload = atob(failingStep.body);
|
|
889
|
+
}
|
|
890
|
+
const callbackMessage = JSON.parse(callbackPayload);
|
|
891
|
+
if (!(callbackMessage.status >= 200 && callbackMessage.status < 300) && callbackMessage.maxRetries && callbackMessage.retried !== callbackMessage.maxRetries) {
|
|
892
|
+
await middlewareManager?.dispatchDebug("onWarning", {
|
|
893
|
+
warning: `Third party call returned status ${callbackMessage.status}. Retrying (${callbackMessage.retried} out of ${callbackMessage.maxRetries}).`
|
|
894
|
+
});
|
|
895
|
+
console.warn(
|
|
896
|
+
`Workflow Warning: "context.call" failed with status ${callbackMessage.status} and will retry (retried ${callbackMessage.retried ?? 0} out of ${callbackMessage.maxRetries} times). Error Message:
|
|
897
|
+
${atob(callbackMessage.body ?? "")}`
|
|
898
|
+
);
|
|
899
|
+
return ok("call-will-retry");
|
|
900
|
+
}
|
|
901
|
+
const workflowRunId = request.headers.get(WORKFLOW_ID_HEADER);
|
|
902
|
+
const stepIdString = request.headers.get("Upstash-Workflow-StepId");
|
|
903
|
+
const stepName = request.headers.get("Upstash-Workflow-StepName");
|
|
904
|
+
const stepType = request.headers.get("Upstash-Workflow-StepType");
|
|
905
|
+
const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
|
|
906
|
+
const contentType = request.headers.get("Upstash-Workflow-ContentType");
|
|
907
|
+
const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
|
|
908
|
+
if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
|
|
909
|
+
throw new Error(
|
|
910
|
+
`Missing info in callback message source header: ${JSON.stringify({
|
|
911
|
+
workflowRunId,
|
|
912
|
+
stepIdString,
|
|
913
|
+
stepName,
|
|
914
|
+
stepType,
|
|
915
|
+
concurrentString,
|
|
916
|
+
contentType
|
|
917
|
+
})}`
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
const userHeaders = recreateUserHeaders(request.headers);
|
|
921
|
+
const { headers: requestHeaders } = getHeaders({
|
|
922
|
+
initHeaderValue: "false",
|
|
923
|
+
workflowConfig: {
|
|
924
|
+
workflowRunId,
|
|
925
|
+
workflowUrl,
|
|
926
|
+
telemetry: telemetry2
|
|
927
|
+
},
|
|
928
|
+
userHeaders,
|
|
929
|
+
invokeCount: Number(invokeCount)
|
|
930
|
+
});
|
|
931
|
+
const callResponse = {
|
|
932
|
+
status: callbackMessage.status,
|
|
933
|
+
body: atob(callbackMessage.body ?? ""),
|
|
934
|
+
header: callbackMessage.header
|
|
935
|
+
};
|
|
936
|
+
const callResultStep = {
|
|
937
|
+
stepId: Number(stepIdString),
|
|
938
|
+
stepName,
|
|
939
|
+
stepType,
|
|
940
|
+
out: JSON.stringify(callResponse),
|
|
941
|
+
concurrent: Number(concurrentString)
|
|
942
|
+
};
|
|
943
|
+
await middlewareManager?.dispatchDebug("onInfo", {
|
|
944
|
+
info: `Submitting third party call result, step ${stepName} (${stepIdString}).`
|
|
945
|
+
});
|
|
946
|
+
await client.publishJSON({
|
|
947
|
+
headers: requestHeaders,
|
|
948
|
+
method: "POST",
|
|
949
|
+
body: callResultStep,
|
|
950
|
+
url: workflowUrl
|
|
951
|
+
});
|
|
952
|
+
await middlewareManager?.dispatchDebug("onInfo", {
|
|
953
|
+
info: `Third party call result submitted successfully, step ${stepName} (${stepIdString}).`
|
|
954
|
+
});
|
|
955
|
+
return ok("is-call-return");
|
|
956
|
+
} else {
|
|
957
|
+
return ok("continue-workflow");
|
|
958
|
+
}
|
|
959
|
+
} catch (error) {
|
|
960
|
+
const isCallReturn = request.headers.get("Upstash-Workflow-Callback");
|
|
961
|
+
return err(
|
|
962
|
+
new WorkflowError(`Error when handling call return (isCallReturn=${isCallReturn}): ${error}`)
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
var getTelemetryHeaders = (telemetry2) => {
|
|
967
|
+
return {
|
|
968
|
+
[TELEMETRY_HEADER_SDK]: telemetry2.sdk,
|
|
969
|
+
[TELEMETRY_HEADER_FRAMEWORK]: telemetry2.framework ?? "unknown",
|
|
970
|
+
[TELEMETRY_HEADER_RUNTIME]: telemetry2.runtime ?? "unknown"
|
|
971
|
+
};
|
|
972
|
+
};
|
|
973
|
+
var verifyRequest = async (body, signature, verifier) => {
|
|
974
|
+
if (!verifier) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
try {
|
|
978
|
+
if (!signature) {
|
|
979
|
+
throw new Error("`Upstash-Signature` header is not passed.");
|
|
980
|
+
}
|
|
981
|
+
const isValid = await verifier.verify({
|
|
982
|
+
body,
|
|
983
|
+
signature
|
|
984
|
+
});
|
|
985
|
+
if (!isValid) {
|
|
986
|
+
throw new Error("Signature in `Upstash-Signature` header is not valid");
|
|
987
|
+
}
|
|
988
|
+
} catch (error) {
|
|
989
|
+
throw new WorkflowError(
|
|
990
|
+
`Failed to verify that the Workflow request comes from QStash: ${error}
|
|
991
|
+
|
|
992
|
+
If signature is missing, trigger the workflow endpoint by publishing your request to QStash instead of calling it directly.
|
|
993
|
+
|
|
994
|
+
If you want to disable QStash Verification, you should clear env variables QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY`
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
// src/context/steps.ts
|
|
1000
|
+
var BaseLazyStep = class _BaseLazyStep {
|
|
1001
|
+
stepName;
|
|
1002
|
+
context;
|
|
1003
|
+
constructor(context, stepName) {
|
|
1004
|
+
this.context = context;
|
|
1005
|
+
if (!stepName) {
|
|
1006
|
+
throw new WorkflowError(
|
|
1007
|
+
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
if (typeof stepName !== "string") {
|
|
1011
|
+
throw new WorkflowError(
|
|
1012
|
+
`A workflow step name must be a string. Received "${stepName}" (${typeof stepName}).`
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
this.stepName = stepName;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* parse the out field of a step result.
|
|
1019
|
+
*
|
|
1020
|
+
* will be called when returning the steps to the context from auto executor
|
|
1021
|
+
*
|
|
1022
|
+
* @param step step
|
|
1023
|
+
* @returns parsed out field
|
|
1024
|
+
*/
|
|
1025
|
+
parseOut(step) {
|
|
1026
|
+
const out = step.out;
|
|
1027
|
+
if (out === void 0) {
|
|
1028
|
+
if (this.allowUndefinedOut) {
|
|
1029
|
+
return this.handleUndefinedOut(step);
|
|
1030
|
+
} else {
|
|
1031
|
+
throw new WorkflowError(
|
|
1032
|
+
`Error while parsing output of ${this.stepType} step. Expected a string, but got: undefined`
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
if (typeof out === "object") {
|
|
1037
|
+
console.warn(
|
|
1038
|
+
`Error while parsing ${this.stepType} step output. Expected a string, but got object. Please reach out to Upstash Support.`
|
|
1039
|
+
);
|
|
1040
|
+
return out;
|
|
1041
|
+
}
|
|
1042
|
+
if (typeof out !== "string") {
|
|
1043
|
+
throw new WorkflowError(
|
|
1044
|
+
`Error while parsing output of ${this.stepType} step. Expected a string or undefined, but got: ${typeof out}`
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
return this.safeParseOut(out, step);
|
|
1048
|
+
}
|
|
1049
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1050
|
+
safeParseOut(out, step) {
|
|
1051
|
+
return _BaseLazyStep.tryParsing(out);
|
|
1052
|
+
}
|
|
1053
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1054
|
+
handleUndefinedOut(step) {
|
|
1055
|
+
return void 0;
|
|
1056
|
+
}
|
|
1057
|
+
static tryParsing(stepOut) {
|
|
1058
|
+
try {
|
|
1059
|
+
return JSON.parse(stepOut);
|
|
1060
|
+
} catch {
|
|
1061
|
+
return stepOut;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
getBody({ step }) {
|
|
1065
|
+
step.out = JSON.stringify(step.out);
|
|
1066
|
+
return JSON.stringify(step);
|
|
1067
|
+
}
|
|
1068
|
+
getHeaders({ context, telemetry: telemetry2, invokeCount, step }) {
|
|
1069
|
+
return getHeaders({
|
|
1070
|
+
initHeaderValue: "false",
|
|
1071
|
+
workflowConfig: {
|
|
1072
|
+
workflowRunId: context.workflowRunId,
|
|
1073
|
+
workflowUrl: context.url,
|
|
1074
|
+
useJSONContent: false,
|
|
1075
|
+
telemetry: telemetry2
|
|
1076
|
+
},
|
|
1077
|
+
userHeaders: context.headers,
|
|
1078
|
+
invokeCount,
|
|
1079
|
+
stepInfo: {
|
|
1080
|
+
step,
|
|
1081
|
+
lazyStep: this
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
async submitStep({ context, body, headers }) {
|
|
1086
|
+
return await context.qstashClient.batch([
|
|
1087
|
+
{
|
|
1088
|
+
body,
|
|
1089
|
+
headers,
|
|
1090
|
+
method: "POST",
|
|
1091
|
+
url: context.url
|
|
1092
|
+
}
|
|
1093
|
+
]);
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
var LazyFunctionStep = class extends BaseLazyStep {
|
|
1097
|
+
stepFunction;
|
|
1098
|
+
stepType = "Run";
|
|
1099
|
+
allowUndefinedOut = true;
|
|
1100
|
+
constructor(context, stepName, stepFunction) {
|
|
1101
|
+
super(context, stepName);
|
|
1102
|
+
this.stepFunction = stepFunction;
|
|
1103
|
+
}
|
|
1104
|
+
getPlanStep(concurrent, targetStep) {
|
|
1105
|
+
return {
|
|
1106
|
+
stepId: 0,
|
|
1107
|
+
stepName: this.stepName,
|
|
1108
|
+
stepType: this.stepType,
|
|
1109
|
+
concurrent,
|
|
1110
|
+
targetStep
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
async getResultStep(concurrent, stepId) {
|
|
1114
|
+
let result = this.stepFunction();
|
|
1115
|
+
if (result instanceof Promise) {
|
|
1116
|
+
result = await result;
|
|
1117
|
+
}
|
|
1118
|
+
return {
|
|
1119
|
+
stepId,
|
|
1120
|
+
stepName: this.stepName,
|
|
1121
|
+
stepType: this.stepType,
|
|
1122
|
+
out: result,
|
|
1123
|
+
concurrent
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
var LazySleepStep = class extends BaseLazyStep {
|
|
1128
|
+
sleep;
|
|
1129
|
+
stepType = "SleepFor";
|
|
1130
|
+
allowUndefinedOut = true;
|
|
1131
|
+
constructor(context, stepName, sleep) {
|
|
1132
|
+
super(context, stepName);
|
|
1133
|
+
this.sleep = sleep;
|
|
1134
|
+
}
|
|
1135
|
+
getPlanStep(concurrent, targetStep) {
|
|
1136
|
+
return {
|
|
1137
|
+
stepId: 0,
|
|
1138
|
+
stepName: this.stepName,
|
|
1139
|
+
stepType: this.stepType,
|
|
1140
|
+
sleepFor: this.sleep,
|
|
1141
|
+
concurrent,
|
|
1142
|
+
targetStep
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
async getResultStep(concurrent, stepId) {
|
|
1146
|
+
return await Promise.resolve({
|
|
1147
|
+
stepId,
|
|
1148
|
+
stepName: this.stepName,
|
|
1149
|
+
stepType: this.stepType,
|
|
1150
|
+
sleepFor: this.sleep,
|
|
1151
|
+
concurrent
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
async submitStep({ context, body, headers, isParallel }) {
|
|
1155
|
+
return await context.qstashClient.batch([
|
|
1156
|
+
{
|
|
1157
|
+
body,
|
|
1158
|
+
headers,
|
|
1159
|
+
method: "POST",
|
|
1160
|
+
url: context.url,
|
|
1161
|
+
delay: isParallel ? void 0 : this.sleep
|
|
1162
|
+
}
|
|
1163
|
+
]);
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
var LazySleepUntilStep = class extends BaseLazyStep {
|
|
1167
|
+
sleepUntil;
|
|
1168
|
+
stepType = "SleepUntil";
|
|
1169
|
+
allowUndefinedOut = true;
|
|
1170
|
+
constructor(context, stepName, sleepUntil) {
|
|
1171
|
+
super(context, stepName);
|
|
1172
|
+
this.sleepUntil = sleepUntil;
|
|
1173
|
+
}
|
|
1174
|
+
getPlanStep(concurrent, targetStep) {
|
|
1175
|
+
return {
|
|
1176
|
+
stepId: 0,
|
|
1177
|
+
stepName: this.stepName,
|
|
1178
|
+
stepType: this.stepType,
|
|
1179
|
+
sleepUntil: this.sleepUntil,
|
|
1180
|
+
concurrent,
|
|
1181
|
+
targetStep
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
async getResultStep(concurrent, stepId) {
|
|
1185
|
+
return await Promise.resolve({
|
|
1186
|
+
stepId,
|
|
1187
|
+
stepName: this.stepName,
|
|
1188
|
+
stepType: this.stepType,
|
|
1189
|
+
sleepUntil: this.sleepUntil,
|
|
1190
|
+
concurrent
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
safeParseOut() {
|
|
1194
|
+
return void 0;
|
|
1195
|
+
}
|
|
1196
|
+
async submitStep({ context, body, headers, isParallel }) {
|
|
1197
|
+
return await context.qstashClient.batch([
|
|
1198
|
+
{
|
|
1199
|
+
body,
|
|
1200
|
+
headers,
|
|
1201
|
+
method: "POST",
|
|
1202
|
+
url: context.url,
|
|
1203
|
+
notBefore: isParallel ? void 0 : this.sleepUntil
|
|
1204
|
+
}
|
|
1205
|
+
]);
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
|
|
1209
|
+
url;
|
|
1210
|
+
method;
|
|
1211
|
+
body;
|
|
1212
|
+
headers;
|
|
1213
|
+
retries;
|
|
1214
|
+
retryDelay;
|
|
1215
|
+
timeout;
|
|
1216
|
+
flowControl;
|
|
1217
|
+
stepType = "Call";
|
|
1218
|
+
allowUndefinedOut = false;
|
|
1219
|
+
constructor(params) {
|
|
1220
|
+
super(params.context, params.stepName);
|
|
1221
|
+
this.url = params.url;
|
|
1222
|
+
this.method = params.method ?? "GET";
|
|
1223
|
+
this.body = params.body;
|
|
1224
|
+
this.headers = params.headers ?? {};
|
|
1225
|
+
this.retries = params.retries ?? 0;
|
|
1226
|
+
this.retryDelay = params.retryDelay;
|
|
1227
|
+
this.timeout = params.timeout;
|
|
1228
|
+
this.flowControl = params.flowControl;
|
|
1229
|
+
}
|
|
1230
|
+
getPlanStep(concurrent, targetStep) {
|
|
1231
|
+
return {
|
|
1232
|
+
stepId: 0,
|
|
1233
|
+
stepName: this.stepName,
|
|
1234
|
+
stepType: this.stepType,
|
|
1235
|
+
concurrent,
|
|
1236
|
+
targetStep
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
async getResultStep(concurrent, stepId) {
|
|
1240
|
+
return await Promise.resolve({
|
|
1241
|
+
stepId,
|
|
1242
|
+
stepName: this.stepName,
|
|
1243
|
+
stepType: this.stepType,
|
|
1244
|
+
concurrent,
|
|
1245
|
+
callUrl: this.url,
|
|
1246
|
+
callMethod: this.method,
|
|
1247
|
+
callBody: this.body,
|
|
1248
|
+
callHeaders: this.headers
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
safeParseOut(out) {
|
|
1252
|
+
const { header, status, body } = JSON.parse(out);
|
|
1253
|
+
const responseHeaders = new Headers(header);
|
|
1254
|
+
if (_LazyCallStep.isText(responseHeaders.get("content-type"))) {
|
|
1255
|
+
const bytes = new Uint8Array(out.length);
|
|
1256
|
+
for (let i = 0; i < out.length; i++) {
|
|
1257
|
+
bytes[i] = out.charCodeAt(i);
|
|
1258
|
+
}
|
|
1259
|
+
const processedResult = new TextDecoder().decode(bytes);
|
|
1260
|
+
const newBody = JSON.parse(processedResult).body;
|
|
1261
|
+
return {
|
|
1262
|
+
status,
|
|
1263
|
+
header,
|
|
1264
|
+
body: BaseLazyStep.tryParsing(newBody)
|
|
1265
|
+
};
|
|
1266
|
+
} else {
|
|
1267
|
+
return { header, status, body };
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
static applicationContentTypes = [
|
|
1271
|
+
"application/json",
|
|
1272
|
+
"application/xml",
|
|
1273
|
+
"application/javascript",
|
|
1274
|
+
"application/x-www-form-urlencoded",
|
|
1275
|
+
"application/xhtml+xml",
|
|
1276
|
+
"application/ld+json",
|
|
1277
|
+
"application/rss+xml",
|
|
1278
|
+
"application/atom+xml"
|
|
1279
|
+
];
|
|
1280
|
+
static isText = (contentTypeHeader) => {
|
|
1281
|
+
if (!contentTypeHeader) {
|
|
1282
|
+
return false;
|
|
1283
|
+
}
|
|
1284
|
+
if (_LazyCallStep.applicationContentTypes.some((type) => contentTypeHeader.includes(type))) {
|
|
1285
|
+
return true;
|
|
1286
|
+
}
|
|
1287
|
+
if (contentTypeHeader.startsWith("text/")) {
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
return false;
|
|
1291
|
+
};
|
|
1292
|
+
getBody({ step }) {
|
|
1293
|
+
if (!step.callUrl) {
|
|
1294
|
+
throw new WorkflowError("Incompatible step received in LazyCallStep.getBody");
|
|
1295
|
+
}
|
|
1296
|
+
return JSON.stringify(step.callBody);
|
|
1297
|
+
}
|
|
1298
|
+
getHeaders({ context, telemetry: telemetry2, invokeCount, step }) {
|
|
1299
|
+
const { headers, contentType } = super.getHeaders({ context, telemetry: telemetry2, invokeCount, step });
|
|
1300
|
+
headers["Upstash-Retries"] = this.retries.toString();
|
|
1301
|
+
if (this.retryDelay) {
|
|
1302
|
+
headers["Upstash-Retry-Delay"] = this.retryDelay;
|
|
1303
|
+
}
|
|
1304
|
+
headers[WORKFLOW_FEATURE_HEADER] = "WF_NoDelete,InitialBody";
|
|
1305
|
+
if (this.flowControl) {
|
|
1306
|
+
const { flowControlKey, flowControlValue } = prepareFlowControl(this.flowControl);
|
|
1307
|
+
headers["Upstash-Flow-Control-Key"] = flowControlKey;
|
|
1308
|
+
headers["Upstash-Flow-Control-Value"] = flowControlValue;
|
|
1309
|
+
}
|
|
1310
|
+
if (this.timeout) {
|
|
1311
|
+
headers["Upstash-Timeout"] = this.timeout.toString();
|
|
1312
|
+
}
|
|
1313
|
+
const forwardedHeaders = Object.fromEntries(
|
|
1314
|
+
Object.entries(this.headers).map(([header, value]) => [`Upstash-Forward-${header}`, value])
|
|
1315
|
+
);
|
|
1316
|
+
return {
|
|
1317
|
+
headers: {
|
|
1318
|
+
...headers,
|
|
1319
|
+
...forwardedHeaders,
|
|
1320
|
+
"Upstash-Callback": context.url,
|
|
1321
|
+
"Upstash-Callback-Workflow-RunId": context.workflowRunId,
|
|
1322
|
+
"Upstash-Callback-Workflow-CallType": "fromCallback",
|
|
1323
|
+
"Upstash-Callback-Workflow-Init": "false",
|
|
1324
|
+
"Upstash-Callback-Workflow-Url": context.url,
|
|
1325
|
+
"Upstash-Callback-Feature-Set": "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig",
|
|
1326
|
+
"Upstash-Callback-Forward-Upstash-Workflow-Callback": "true",
|
|
1327
|
+
"Upstash-Callback-Forward-Upstash-Workflow-StepId": step.stepId.toString(),
|
|
1328
|
+
"Upstash-Callback-Forward-Upstash-Workflow-StepName": this.stepName,
|
|
1329
|
+
"Upstash-Callback-Forward-Upstash-Workflow-StepType": this.stepType,
|
|
1330
|
+
"Upstash-Callback-Forward-Upstash-Workflow-Concurrent": step.concurrent.toString(),
|
|
1331
|
+
"Upstash-Callback-Forward-Upstash-Workflow-ContentType": contentType,
|
|
1332
|
+
"Upstash-Workflow-CallType": "toCallback"
|
|
1333
|
+
},
|
|
1334
|
+
contentType
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
async submitStep({ context, headers }) {
|
|
1338
|
+
return await context.qstashClient.batch([
|
|
1339
|
+
{
|
|
1340
|
+
headers,
|
|
1341
|
+
body: this.body,
|
|
1342
|
+
method: this.method,
|
|
1343
|
+
url: this.url,
|
|
1344
|
+
retries: DEFAULT_RETRIES === this.retries ? void 0 : this.retries,
|
|
1345
|
+
retryDelay: this.retryDelay,
|
|
1346
|
+
flowControl: this.flowControl
|
|
1347
|
+
}
|
|
1348
|
+
]);
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
var LazyWaitEventStep = class extends BaseLazyStep {
|
|
1352
|
+
eventId;
|
|
1353
|
+
timeout;
|
|
1354
|
+
allowUndefinedOut = false;
|
|
1355
|
+
constructor(context, stepName, eventId, timeout) {
|
|
1356
|
+
super(context, stepName);
|
|
1357
|
+
this.eventId = eventId;
|
|
1358
|
+
this.timeout = timeout;
|
|
1359
|
+
}
|
|
1360
|
+
getPlanStep(concurrent, targetStep) {
|
|
1361
|
+
return {
|
|
1362
|
+
stepId: 0,
|
|
1363
|
+
stepName: this.stepName,
|
|
1364
|
+
stepType: this.stepType,
|
|
1365
|
+
waitEventId: this.eventId,
|
|
1366
|
+
timeout: this.timeout,
|
|
1367
|
+
concurrent,
|
|
1368
|
+
targetStep
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
async getResultStep(concurrent, stepId) {
|
|
1372
|
+
return await Promise.resolve({
|
|
1373
|
+
stepId,
|
|
1374
|
+
stepName: this.stepName,
|
|
1375
|
+
stepType: this.stepType,
|
|
1376
|
+
waitEventId: this.eventId,
|
|
1377
|
+
timeout: this.timeout,
|
|
1378
|
+
concurrent
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
getHeaders({ context, telemetry: telemetry2, invokeCount, step }) {
|
|
1382
|
+
const headers = super.getHeaders({ context, telemetry: telemetry2, invokeCount, step });
|
|
1383
|
+
headers.headers["Upstash-Workflow-CallType"] = "step";
|
|
1384
|
+
return headers;
|
|
1385
|
+
}
|
|
1386
|
+
getBody({ context, step, headers, telemetry: telemetry2 }) {
|
|
1387
|
+
if (!step.waitEventId) {
|
|
1388
|
+
throw new WorkflowError("Incompatible step received in LazyWaitForEventStep.getBody");
|
|
1389
|
+
}
|
|
1390
|
+
const timeoutHeaders = {
|
|
1391
|
+
// to include user headers:
|
|
1392
|
+
...Object.fromEntries(Object.entries(headers).map(([header, value]) => [header, [value]])),
|
|
1393
|
+
// to include telemetry headers:
|
|
1394
|
+
...telemetry2 ? Object.fromEntries(
|
|
1395
|
+
Object.entries(getTelemetryHeaders(telemetry2)).map(([header, value]) => [
|
|
1396
|
+
header,
|
|
1397
|
+
[value]
|
|
1398
|
+
])
|
|
1399
|
+
) : {},
|
|
1400
|
+
// note: using WORKFLOW_ID_HEADER doesn't work, because Runid -> RunId:
|
|
1401
|
+
"Upstash-Workflow-Runid": [context.workflowRunId],
|
|
1402
|
+
[WORKFLOW_INIT_HEADER]: ["false"],
|
|
1403
|
+
[WORKFLOW_URL_HEADER]: [context.url],
|
|
1404
|
+
"Upstash-Workflow-CallType": ["step"]
|
|
1405
|
+
};
|
|
1406
|
+
const waitBody = {
|
|
1407
|
+
url: context.url,
|
|
1408
|
+
timeout: step.timeout,
|
|
1409
|
+
timeoutBody: void 0,
|
|
1410
|
+
timeoutUrl: context.url,
|
|
1411
|
+
timeoutHeaders,
|
|
1412
|
+
step: {
|
|
1413
|
+
stepId: step.stepId,
|
|
1414
|
+
stepType: this.stepType,
|
|
1415
|
+
stepName: step.stepName,
|
|
1416
|
+
concurrent: step.concurrent,
|
|
1417
|
+
targetStep: step.targetStep
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
return JSON.stringify(waitBody);
|
|
1421
|
+
}
|
|
1422
|
+
async submitStep({ context, body, headers }) {
|
|
1423
|
+
const result = await context.qstashClient.http.request({
|
|
1424
|
+
path: ["v2", "wait", this.eventId],
|
|
1425
|
+
body,
|
|
1426
|
+
headers,
|
|
1427
|
+
method: "POST",
|
|
1428
|
+
parseResponseAsJson: false
|
|
1429
|
+
});
|
|
1430
|
+
return [result];
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
var LazyNotifyStep = class extends LazyFunctionStep {
|
|
1434
|
+
stepType = "Notify";
|
|
1435
|
+
constructor(context, stepName, eventId, eventData, requester) {
|
|
1436
|
+
super(context, stepName, async () => {
|
|
1437
|
+
const notifyResponse = await makeNotifyRequest(requester, eventId, eventData);
|
|
1438
|
+
return {
|
|
1439
|
+
eventId,
|
|
1440
|
+
eventData,
|
|
1441
|
+
notifyResponse
|
|
1442
|
+
};
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
safeParseOut(out) {
|
|
1446
|
+
const result = JSON.parse(out);
|
|
1447
|
+
return {
|
|
1448
|
+
...result,
|
|
1449
|
+
eventData: BaseLazyStep.tryParsing(result.eventData)
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
var LazyInvokeStep = class extends BaseLazyStep {
|
|
1454
|
+
stepType = "Invoke";
|
|
1455
|
+
params;
|
|
1456
|
+
allowUndefinedOut = false;
|
|
1457
|
+
/**
|
|
1458
|
+
* workflow id of the invoked workflow
|
|
1459
|
+
*/
|
|
1460
|
+
workflowId;
|
|
1461
|
+
constructor(context, stepName, params) {
|
|
1462
|
+
super(context, stepName);
|
|
1463
|
+
this.params = params;
|
|
1464
|
+
const { workflowId } = params.workflow;
|
|
1465
|
+
if (!workflowId) {
|
|
1466
|
+
throw new WorkflowError("You can only invoke workflow which has a workflowId");
|
|
1467
|
+
}
|
|
1468
|
+
this.workflowId = workflowId;
|
|
1469
|
+
}
|
|
1470
|
+
getPlanStep(concurrent, targetStep) {
|
|
1471
|
+
return {
|
|
1472
|
+
stepId: 0,
|
|
1473
|
+
stepName: this.stepName,
|
|
1474
|
+
stepType: this.stepType,
|
|
1475
|
+
concurrent,
|
|
1476
|
+
targetStep
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* won't be used as it's the server who will add the result step
|
|
1481
|
+
* in Invoke step.
|
|
1482
|
+
*/
|
|
1483
|
+
getResultStep(concurrent, stepId) {
|
|
1484
|
+
return Promise.resolve({
|
|
1485
|
+
stepId,
|
|
1486
|
+
stepName: this.stepName,
|
|
1487
|
+
stepType: this.stepType,
|
|
1488
|
+
concurrent
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
safeParseOut(out) {
|
|
1492
|
+
const result = JSON.parse(out);
|
|
1493
|
+
return {
|
|
1494
|
+
...result,
|
|
1495
|
+
body: BaseLazyStep.tryParsing(result.body)
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
getBody({ context, step, telemetry: telemetry2, invokeCount }) {
|
|
1499
|
+
const { headers: invokerHeaders } = getHeaders({
|
|
1500
|
+
initHeaderValue: "false",
|
|
1501
|
+
workflowConfig: {
|
|
1502
|
+
workflowRunId: context.workflowRunId,
|
|
1503
|
+
workflowUrl: context.url,
|
|
1504
|
+
telemetry: telemetry2,
|
|
1505
|
+
useJSONContent: false
|
|
1506
|
+
},
|
|
1507
|
+
userHeaders: context.headers,
|
|
1508
|
+
invokeCount
|
|
1509
|
+
});
|
|
1510
|
+
context.qstashClient.http.headers?.forEach((value, key) => {
|
|
1511
|
+
invokerHeaders[key] = value;
|
|
1512
|
+
});
|
|
1513
|
+
invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
|
|
1514
|
+
const request = {
|
|
1515
|
+
body: stringifyBody(this.params.body),
|
|
1516
|
+
headers: Object.fromEntries(
|
|
1517
|
+
Object.entries(invokerHeaders).map((pairs) => [pairs[0], [pairs[1]]])
|
|
1518
|
+
),
|
|
1519
|
+
workflowRunId: context.workflowRunId,
|
|
1520
|
+
workflowRunCreatedAt: context.workflowRunCreatedAt,
|
|
1521
|
+
workflowUrl: context.url,
|
|
1522
|
+
step
|
|
1523
|
+
};
|
|
1524
|
+
return JSON.stringify(request);
|
|
1525
|
+
}
|
|
1526
|
+
getHeaders({ context, telemetry: telemetry2, invokeCount }) {
|
|
1527
|
+
const {
|
|
1528
|
+
workflow,
|
|
1529
|
+
headers = {},
|
|
1530
|
+
workflowRunId,
|
|
1531
|
+
retries,
|
|
1532
|
+
retryDelay,
|
|
1533
|
+
flowControl,
|
|
1534
|
+
label
|
|
1535
|
+
} = this.params;
|
|
1536
|
+
const newUrl = context.url.replace(/[^/]+$/, this.workflowId);
|
|
1537
|
+
const { headers: triggerHeaders, contentType } = getHeaders({
|
|
1538
|
+
initHeaderValue: "true",
|
|
1539
|
+
workflowConfig: {
|
|
1540
|
+
workflowRunId: getWorkflowRunId(workflowRunId),
|
|
1541
|
+
workflowUrl: newUrl,
|
|
1542
|
+
retries,
|
|
1543
|
+
retryDelay,
|
|
1544
|
+
telemetry: telemetry2,
|
|
1545
|
+
failureUrl: newUrl,
|
|
1546
|
+
flowControl,
|
|
1547
|
+
useJSONContent: workflow.useJSONContent ?? false
|
|
1548
|
+
},
|
|
1549
|
+
invokeCount: invokeCount + 1,
|
|
1550
|
+
userHeaders: new Headers(headers)
|
|
1551
|
+
});
|
|
1552
|
+
triggerHeaders["Upstash-Workflow-Invoke"] = "true";
|
|
1553
|
+
if (label) {
|
|
1554
|
+
triggerHeaders[WORKFLOW_LABEL_HEADER] = label;
|
|
1555
|
+
triggerHeaders[`upstash-forward-${WORKFLOW_LABEL_HEADER}`] = label;
|
|
1556
|
+
}
|
|
1557
|
+
return { headers: triggerHeaders, contentType };
|
|
1558
|
+
}
|
|
1559
|
+
async submitStep({ context, body, headers }) {
|
|
1560
|
+
const newUrl = context.url.replace(/[^/]+$/, this.workflowId);
|
|
1561
|
+
const result = await context.qstashClient.publish({
|
|
1562
|
+
headers,
|
|
1563
|
+
method: "POST",
|
|
1564
|
+
body,
|
|
1565
|
+
url: newUrl
|
|
1566
|
+
});
|
|
1567
|
+
return [result];
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
var LazyCreateWebhookStep = class extends BaseLazyStep {
|
|
1571
|
+
stepType = "CreateWebhook";
|
|
1572
|
+
allowUndefinedOut = false;
|
|
1573
|
+
getPlanStep(concurrent, targetStep) {
|
|
1574
|
+
return {
|
|
1575
|
+
stepId: 0,
|
|
1576
|
+
stepName: this.stepName,
|
|
1577
|
+
stepType: this.stepType,
|
|
1578
|
+
concurrent,
|
|
1579
|
+
targetStep
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
async getResultStep(concurrent, stepId) {
|
|
1583
|
+
return {
|
|
1584
|
+
stepId,
|
|
1585
|
+
stepName: this.stepName,
|
|
1586
|
+
stepType: this.stepType,
|
|
1587
|
+
out: void 0,
|
|
1588
|
+
concurrent
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
getBody({ step, context }) {
|
|
1592
|
+
const userId = getUserIdFromToken(context.qstashClient);
|
|
1593
|
+
const workflowRunId = context.workflowRunId;
|
|
1594
|
+
const eventId = getEventId();
|
|
1595
|
+
const qstashUrl = getQStashUrl(this.context.qstashClient);
|
|
1596
|
+
return JSON.stringify({
|
|
1597
|
+
...step,
|
|
1598
|
+
out: JSON.stringify({
|
|
1599
|
+
webhookUrl: `${qstashUrl}/v2/workflows/hooks/${userId}/${workflowRunId}/${eventId}`,
|
|
1600
|
+
eventId
|
|
1601
|
+
})
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
var LazyWaitForWebhookStep = class extends LazyWaitEventStep {
|
|
1606
|
+
stepType = "WaitForWebhook";
|
|
1607
|
+
allowUndefinedOut = true;
|
|
1608
|
+
constructor(context, stepName, webhook, timeout) {
|
|
1609
|
+
super(context, stepName, webhook.eventId, timeout);
|
|
1610
|
+
}
|
|
1611
|
+
safeParseOut(out) {
|
|
1612
|
+
const eventData = decodeBase64(out);
|
|
1613
|
+
const parsedEventData = BaseLazyStep.tryParsing(eventData);
|
|
1614
|
+
const body = parsedEventData.body;
|
|
1615
|
+
const parsedBody = typeof body === "string" ? decodeBase64(body) : void 0;
|
|
1616
|
+
const request = new Request(
|
|
1617
|
+
`${parsedEventData.proto}://${parsedEventData.host}${parsedEventData.url}`,
|
|
1618
|
+
{
|
|
1619
|
+
method: parsedEventData.method,
|
|
1620
|
+
headers: parsedEventData.header,
|
|
1621
|
+
body: parsedBody
|
|
1622
|
+
}
|
|
1623
|
+
);
|
|
1624
|
+
return {
|
|
1625
|
+
request,
|
|
1626
|
+
timeout: false
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
handleUndefinedOut() {
|
|
1630
|
+
return {
|
|
1631
|
+
timeout: true,
|
|
1632
|
+
request: void 0
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
};
|
|
1636
|
+
var LazyWaitForEventStep = class extends LazyWaitEventStep {
|
|
1637
|
+
stepType = "Wait";
|
|
1638
|
+
allowUndefinedOut = true;
|
|
1639
|
+
parseWaitForEventOut(out, waitTimeout) {
|
|
1640
|
+
return {
|
|
1641
|
+
eventData: out ? BaseLazyStep.tryParsing(decodeBase64(out)) : void 0,
|
|
1642
|
+
timeout: waitTimeout ?? false
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
safeParseOut(out, step) {
|
|
1646
|
+
return this.parseWaitForEventOut(out, step.waitTimeout);
|
|
1647
|
+
}
|
|
1648
|
+
handleUndefinedOut(step) {
|
|
1649
|
+
return this.parseWaitForEventOut(void 0, step.waitTimeout);
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
|
|
1653
|
+
// src/qstash/headers.ts
|
|
1654
|
+
var WorkflowHeaders = class {
|
|
1655
|
+
userHeaders;
|
|
1656
|
+
workflowConfig;
|
|
1657
|
+
invokeCount;
|
|
1658
|
+
initHeaderValue;
|
|
1659
|
+
stepInfo;
|
|
1660
|
+
headers;
|
|
1661
|
+
/**
|
|
1662
|
+
* @param params workflow header parameters
|
|
1663
|
+
*/
|
|
1664
|
+
constructor({
|
|
1665
|
+
userHeaders,
|
|
1666
|
+
workflowConfig,
|
|
1667
|
+
invokeCount,
|
|
1668
|
+
initHeaderValue,
|
|
1669
|
+
stepInfo
|
|
1670
|
+
}) {
|
|
1671
|
+
this.userHeaders = userHeaders;
|
|
1672
|
+
this.workflowConfig = workflowConfig;
|
|
1673
|
+
this.invokeCount = invokeCount;
|
|
1674
|
+
this.initHeaderValue = initHeaderValue;
|
|
1675
|
+
this.stepInfo = stepInfo;
|
|
1676
|
+
this.headers = {
|
|
1677
|
+
rawHeaders: {},
|
|
1678
|
+
workflowHeaders: {},
|
|
1679
|
+
failureHeaders: {}
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
getHeaders() {
|
|
1683
|
+
this.addBaseHeaders();
|
|
1684
|
+
this.addRetries();
|
|
1685
|
+
this.addRetryDelay();
|
|
1686
|
+
this.addFlowControl();
|
|
1687
|
+
this.addUserHeaders();
|
|
1688
|
+
this.addInvokeCount();
|
|
1689
|
+
this.addFailureUrl();
|
|
1690
|
+
const contentType = this.addContentType();
|
|
1691
|
+
return this.prefixHeaders(contentType);
|
|
1692
|
+
}
|
|
1693
|
+
addBaseHeaders() {
|
|
1694
|
+
this.headers.rawHeaders = {
|
|
1695
|
+
...this.headers.rawHeaders,
|
|
1696
|
+
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
|
|
1697
|
+
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
|
|
1698
|
+
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
|
|
1699
|
+
[WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig",
|
|
1700
|
+
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
1701
|
+
...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
|
|
1702
|
+
};
|
|
1703
|
+
if (this.stepInfo?.lazyStep.stepType !== "Call") {
|
|
1704
|
+
this.headers.rawHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
addInvokeCount() {
|
|
1708
|
+
if (this.invokeCount === void 0 || this.invokeCount === 0) {
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
const invokeCount = this.invokeCount.toString();
|
|
1712
|
+
this.headers.workflowHeaders[`Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`] = invokeCount;
|
|
1713
|
+
if (this.workflowConfig.failureUrl) {
|
|
1714
|
+
this.headers.failureHeaders[`Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`] = invokeCount;
|
|
1715
|
+
}
|
|
1716
|
+
if (this.stepInfo?.lazyStep instanceof LazyCallStep) {
|
|
1717
|
+
this.headers.rawHeaders[`Upstash-Forward-${WORKFLOW_INVOKE_COUNT_HEADER}`] = invokeCount;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
addRetries() {
|
|
1721
|
+
if (this.workflowConfig.retries === void 0 || this.workflowConfig.retries === DEFAULT_RETRIES) {
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
const retries = this.workflowConfig.retries.toString();
|
|
1725
|
+
this.headers.workflowHeaders["Retries"] = retries;
|
|
1726
|
+
if (this.workflowConfig.failureUrl) {
|
|
1727
|
+
this.headers.failureHeaders["Retries"] = retries;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
addRetryDelay() {
|
|
1731
|
+
if (this.workflowConfig.retryDelay === void 0 || this.workflowConfig.retryDelay === "") {
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
const retryDelay = this.workflowConfig.retryDelay.toString();
|
|
1735
|
+
this.headers.workflowHeaders["Retry-Delay"] = retryDelay;
|
|
1736
|
+
if (this.workflowConfig.failureUrl) {
|
|
1737
|
+
this.headers.failureHeaders["Retry-Delay"] = retryDelay;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
addFlowControl() {
|
|
1741
|
+
if (!this.workflowConfig.flowControl) {
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
const { flowControlKey, flowControlValue } = prepareFlowControl(
|
|
1745
|
+
this.workflowConfig.flowControl
|
|
1746
|
+
);
|
|
1747
|
+
this.headers.workflowHeaders["Flow-Control-Key"] = flowControlKey;
|
|
1748
|
+
this.headers.workflowHeaders["Flow-Control-Value"] = flowControlValue;
|
|
1749
|
+
if (this.workflowConfig.failureUrl) {
|
|
1750
|
+
this.headers.failureHeaders["Flow-Control-Key"] = flowControlKey;
|
|
1751
|
+
this.headers.failureHeaders["Flow-Control-Value"] = flowControlValue;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
addUserHeaders() {
|
|
1755
|
+
for (const [key, value] of this.userHeaders.entries()) {
|
|
1756
|
+
const forwardKey = `Forward-${key}`;
|
|
1757
|
+
this.headers.workflowHeaders[forwardKey] = value;
|
|
1758
|
+
if (this.workflowConfig.failureUrl) {
|
|
1759
|
+
this.headers.failureHeaders[forwardKey] = value;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
addFailureUrl() {
|
|
1764
|
+
if (!this.workflowConfig.failureUrl) {
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
this.headers.workflowHeaders["Failure-Callback"] = this.workflowConfig.failureUrl;
|
|
1768
|
+
this.headers.failureHeaders[`Forward-${WORKFLOW_FAILURE_HEADER}`] = "true";
|
|
1769
|
+
this.headers.failureHeaders[`Forward-${WORKFLOW_FAILURE_CALLBACK_HEADER}`] = "true";
|
|
1770
|
+
this.headers.failureHeaders["Workflow-Runid"] = this.workflowConfig.workflowRunId;
|
|
1771
|
+
this.headers.failureHeaders["Workflow-Init"] = "false";
|
|
1772
|
+
this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
|
|
1773
|
+
this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
|
|
1774
|
+
this.headers.failureHeaders["Feature-Set"] = "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig";
|
|
1775
|
+
if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
|
|
1776
|
+
this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
|
|
1777
|
+
}
|
|
1778
|
+
if (this.workflowConfig.retryDelay !== void 0 && this.workflowConfig.retryDelay !== "") {
|
|
1779
|
+
this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
addContentType() {
|
|
1783
|
+
if (this.workflowConfig.useJSONContent) {
|
|
1784
|
+
this.headers.rawHeaders["content-type"] = "application/json";
|
|
1785
|
+
return "application/json";
|
|
1786
|
+
}
|
|
1787
|
+
const callHeaders = new Headers(
|
|
1788
|
+
this.stepInfo?.lazyStep instanceof LazyCallStep ? this.stepInfo.lazyStep.headers : {}
|
|
1789
|
+
);
|
|
1790
|
+
const contentType = (callHeaders.get("content-type") ? callHeaders.get("content-type") : this.userHeaders?.get("Content-Type") ? this.userHeaders.get("Content-Type") : void 0) ?? DEFAULT_CONTENT_TYPE;
|
|
1791
|
+
this.headers.rawHeaders["content-type"] = contentType;
|
|
1792
|
+
return contentType;
|
|
1793
|
+
}
|
|
1794
|
+
prefixHeaders(contentType) {
|
|
1795
|
+
const { rawHeaders, workflowHeaders, failureHeaders } = this.headers;
|
|
1796
|
+
const isCall = this.stepInfo?.lazyStep.stepType === "Call";
|
|
1797
|
+
return {
|
|
1798
|
+
headers: {
|
|
1799
|
+
...rawHeaders,
|
|
1800
|
+
...addPrefixToHeaders(workflowHeaders, isCall ? "Upstash-Callback-" : "Upstash-"),
|
|
1801
|
+
...addPrefixToHeaders(failureHeaders, "Upstash-Failure-Callback-"),
|
|
1802
|
+
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {}
|
|
1803
|
+
},
|
|
1804
|
+
contentType
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
function addPrefixToHeaders(headers, prefix) {
|
|
1809
|
+
const prefixedHeaders = {};
|
|
1810
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1811
|
+
prefixedHeaders[`${prefix}${key}`] = value;
|
|
1812
|
+
}
|
|
1813
|
+
return prefixedHeaders;
|
|
1814
|
+
}
|
|
1815
|
+
var prepareFlowControl = (flowControl) => {
|
|
1816
|
+
const parallelism = flowControl.parallelism?.toString();
|
|
1817
|
+
const rate = (flowControl.rate ?? flowControl.ratePerSecond)?.toString();
|
|
1818
|
+
const period = typeof flowControl.period === "number" ? `${flowControl.period}s` : flowControl.period;
|
|
1819
|
+
const controlValue = [
|
|
1820
|
+
parallelism ? `parallelism=${parallelism}` : void 0,
|
|
1821
|
+
rate ? `rate=${rate}` : void 0,
|
|
1822
|
+
period ? `period=${period}` : void 0
|
|
1823
|
+
].filter(Boolean);
|
|
1824
|
+
if (controlValue.length === 0) {
|
|
1825
|
+
throw new import_qstash4.QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
|
|
1826
|
+
}
|
|
1827
|
+
return {
|
|
1828
|
+
flowControlKey: flowControl.key,
|
|
1829
|
+
flowControlValue: controlValue.join(", ")
|
|
1830
|
+
};
|
|
1831
|
+
};
|
|
1832
|
+
var getHeaders = (params) => {
|
|
1833
|
+
const workflowHeaders = new WorkflowHeaders(params);
|
|
1834
|
+
return workflowHeaders.getHeaders();
|
|
1835
|
+
};
|
|
1836
|
+
|
|
1837
|
+
// src/qstash/submit-steps.ts
|
|
1838
|
+
var submitParallelSteps = async ({
|
|
1839
|
+
context,
|
|
1840
|
+
steps,
|
|
1841
|
+
initialStepCount,
|
|
1842
|
+
invokeCount,
|
|
1843
|
+
telemetry: telemetry2,
|
|
1844
|
+
dispatchDebug
|
|
1845
|
+
}) => {
|
|
1846
|
+
const planSteps = steps.map(
|
|
1847
|
+
(step, index) => step.getPlanStep(steps.length, initialStepCount + index)
|
|
1848
|
+
);
|
|
1849
|
+
await dispatchDebug("onInfo", {
|
|
1850
|
+
info: `Submitting ${planSteps.length} parallel steps.`
|
|
1851
|
+
});
|
|
1852
|
+
const result = await context.qstashClient.batch(
|
|
1853
|
+
planSteps.map((planStep) => {
|
|
1854
|
+
const { headers } = getHeaders({
|
|
1855
|
+
initHeaderValue: "false",
|
|
1856
|
+
workflowConfig: {
|
|
1857
|
+
workflowRunId: context.workflowRunId,
|
|
1858
|
+
workflowUrl: context.url,
|
|
1859
|
+
telemetry: telemetry2
|
|
1860
|
+
},
|
|
1861
|
+
userHeaders: context.headers,
|
|
1862
|
+
invokeCount
|
|
1863
|
+
});
|
|
1864
|
+
return {
|
|
1865
|
+
headers,
|
|
1866
|
+
method: "POST",
|
|
1867
|
+
url: context.url,
|
|
1868
|
+
body: JSON.stringify(planStep),
|
|
1869
|
+
notBefore: planStep.sleepUntil,
|
|
1870
|
+
delay: planStep.sleepFor
|
|
1871
|
+
};
|
|
1872
|
+
})
|
|
1873
|
+
);
|
|
1874
|
+
if (result && result.length > 0) {
|
|
1875
|
+
await dispatchDebug("onInfo", {
|
|
1876
|
+
info: `Submitted ${planSteps.length} parallel steps. messageIds: ${result.filter((r) => r).map((r) => r.messageId).join(", ")}.`
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
|
|
1880
|
+
};
|
|
1881
|
+
var submitSingleStep = async ({
|
|
1882
|
+
context,
|
|
1883
|
+
lazyStep,
|
|
1884
|
+
stepId,
|
|
1885
|
+
invokeCount,
|
|
1886
|
+
concurrency,
|
|
1887
|
+
telemetry: telemetry2,
|
|
1888
|
+
dispatchDebug,
|
|
1889
|
+
dispatchLifecycle
|
|
1890
|
+
}) => {
|
|
1891
|
+
await dispatchLifecycle("beforeExecution", {
|
|
1892
|
+
stepName: lazyStep.stepName
|
|
1893
|
+
});
|
|
1894
|
+
const resultStep = await lazyStep.getResultStep(concurrency, stepId);
|
|
1895
|
+
const { headers } = lazyStep.getHeaders({
|
|
1896
|
+
context,
|
|
1897
|
+
step: resultStep,
|
|
1898
|
+
invokeCount,
|
|
1899
|
+
telemetry: telemetry2
|
|
1900
|
+
});
|
|
1901
|
+
const body = lazyStep.getBody({
|
|
1902
|
+
context,
|
|
1903
|
+
step: resultStep,
|
|
1904
|
+
headers,
|
|
1905
|
+
invokeCount,
|
|
1906
|
+
telemetry: telemetry2
|
|
1907
|
+
});
|
|
1908
|
+
const submitResult = await lazyStep.submitStep({
|
|
1909
|
+
context,
|
|
1910
|
+
body,
|
|
1911
|
+
headers,
|
|
1912
|
+
isParallel: concurrency !== NO_CONCURRENCY,
|
|
1913
|
+
invokeCount,
|
|
1914
|
+
step: resultStep,
|
|
1915
|
+
telemetry: telemetry2
|
|
1916
|
+
});
|
|
1917
|
+
if (submitResult && submitResult[0]) {
|
|
1918
|
+
await dispatchDebug("onInfo", {
|
|
1919
|
+
info: `Submitted step "${resultStep.stepName}" with messageId: ${submitResult[0].messageId}.`
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
return resultStep;
|
|
1923
|
+
};
|
|
1924
|
+
|
|
1925
|
+
// src/context/auto-executor.ts
|
|
1926
|
+
var AutoExecutor = class _AutoExecutor {
|
|
1927
|
+
context;
|
|
1928
|
+
promises = /* @__PURE__ */ new WeakMap();
|
|
1929
|
+
activeLazyStepList;
|
|
1930
|
+
nonPlanStepCount;
|
|
1931
|
+
steps;
|
|
1932
|
+
indexInCurrentList = 0;
|
|
1933
|
+
invokeCount;
|
|
1934
|
+
telemetry;
|
|
1935
|
+
dispatchDebug;
|
|
1936
|
+
dispatchLifecycle;
|
|
1937
|
+
stepCount = 0;
|
|
1938
|
+
planStepCount = 0;
|
|
1939
|
+
executingStep = false;
|
|
1940
|
+
/**
|
|
1941
|
+
* @param context workflow context
|
|
1942
|
+
* @param steps list of steps
|
|
1943
|
+
* @param dispatchDebug debug event dispatcher
|
|
1944
|
+
* @param dispatchLifecycle lifecycle event dispatcher
|
|
1945
|
+
* @param telemetry optional telemetry information
|
|
1946
|
+
* @param invokeCount optional invoke count
|
|
1947
|
+
*/
|
|
1948
|
+
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry2, invokeCount) {
|
|
1949
|
+
this.context = context;
|
|
1950
|
+
this.steps = steps;
|
|
1951
|
+
this.dispatchDebug = dispatchDebug;
|
|
1952
|
+
this.dispatchLifecycle = dispatchLifecycle;
|
|
1953
|
+
this.telemetry = telemetry2;
|
|
1954
|
+
this.invokeCount = invokeCount ?? 0;
|
|
1955
|
+
this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Adds the step function to the list of step functions to run in
|
|
1959
|
+
* parallel. After adding the function, defers the execution, so
|
|
1960
|
+
* that if there is another step function to be added, it's also
|
|
1961
|
+
* added.
|
|
1962
|
+
*
|
|
1963
|
+
* After all functions are added, list of functions are executed.
|
|
1964
|
+
* If there is a single function, it's executed by itself. If there
|
|
1965
|
+
* are multiple, they are run in parallel.
|
|
1966
|
+
*
|
|
1967
|
+
* If a function is already executing (this.executingStep), this
|
|
1968
|
+
* means that there is a nested step which is not allowed. In this
|
|
1969
|
+
* case, addStep throws WorkflowError.
|
|
1970
|
+
*
|
|
1971
|
+
* @param stepInfo step plan to add
|
|
1972
|
+
* @returns result of the step function
|
|
1973
|
+
*/
|
|
1974
|
+
async addStep(stepInfo) {
|
|
1975
|
+
if (this.executingStep) {
|
|
1976
|
+
throw new WorkflowError(
|
|
1977
|
+
`A step can not be run inside another step. Tried to run '${stepInfo.stepName}' inside '${this.executingStep}'`
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
this.stepCount += 1;
|
|
1981
|
+
const lazyStepList = this.activeLazyStepList ?? [];
|
|
1982
|
+
if (!this.activeLazyStepList) {
|
|
1983
|
+
this.activeLazyStepList = lazyStepList;
|
|
1984
|
+
this.indexInCurrentList = 0;
|
|
1985
|
+
}
|
|
1986
|
+
lazyStepList.push(stepInfo);
|
|
1987
|
+
const index = this.indexInCurrentList++;
|
|
1988
|
+
const requestComplete = this.deferExecution().then(async () => {
|
|
1989
|
+
if (!this.promises.has(lazyStepList)) {
|
|
1990
|
+
const promise2 = this.getExecutionPromise(lazyStepList);
|
|
1991
|
+
this.promises.set(lazyStepList, promise2);
|
|
1992
|
+
this.activeLazyStepList = void 0;
|
|
1993
|
+
this.planStepCount += lazyStepList.length > 1 ? lazyStepList.length : 0;
|
|
1994
|
+
}
|
|
1995
|
+
const promise = this.promises.get(lazyStepList);
|
|
1996
|
+
return promise;
|
|
1997
|
+
});
|
|
1998
|
+
const result = await requestComplete;
|
|
1999
|
+
return _AutoExecutor.getResult(lazyStepList, result, index);
|
|
2000
|
+
}
|
|
2001
|
+
/**
|
|
2002
|
+
* Wraps a step function to set this.executingStep to step name
|
|
2003
|
+
* before running and set this.executingStep to False after execution
|
|
2004
|
+
* ends.
|
|
2005
|
+
*
|
|
2006
|
+
* this.executingStep allows us to detect nested steps which are not
|
|
2007
|
+
* allowed.
|
|
2008
|
+
*
|
|
2009
|
+
* @param stepName name of the step being wrapped
|
|
2010
|
+
* @param stepFunction step function to wrap
|
|
2011
|
+
* @returns wrapped step function
|
|
2012
|
+
*/
|
|
2013
|
+
wrapStep(stepName, stepFunction) {
|
|
2014
|
+
this.executingStep = stepName;
|
|
2015
|
+
const result = stepFunction();
|
|
2016
|
+
this.executingStep = false;
|
|
2017
|
+
return result;
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Executes a step:
|
|
2021
|
+
* - If the step result is available in the steps, returns the result
|
|
2022
|
+
* - If the result is not available, runs the function
|
|
2023
|
+
* - Sends the result to QStash
|
|
2024
|
+
*
|
|
2025
|
+
* @param lazyStep lazy step to execute
|
|
2026
|
+
* @returns step result
|
|
2027
|
+
*/
|
|
2028
|
+
async runSingle(lazyStep) {
|
|
2029
|
+
if (this.stepCount < this.nonPlanStepCount) {
|
|
2030
|
+
const step = this.steps[this.stepCount + this.planStepCount];
|
|
2031
|
+
validateStep(lazyStep, step);
|
|
2032
|
+
const parsedOut = lazyStep.parseOut(step);
|
|
2033
|
+
const isLastMemoized = this.stepCount + 1 === this.nonPlanStepCount && this.steps.at(-1).stepId !== 0;
|
|
2034
|
+
if (isLastMemoized) {
|
|
2035
|
+
await this.dispatchLifecycle("afterExecution", {
|
|
2036
|
+
stepName: lazyStep.stepName,
|
|
2037
|
+
result: parsedOut
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
return parsedOut;
|
|
2041
|
+
}
|
|
2042
|
+
const resultStep = await submitSingleStep({
|
|
2043
|
+
context: this.context,
|
|
2044
|
+
lazyStep,
|
|
2045
|
+
stepId: this.stepCount,
|
|
2046
|
+
invokeCount: this.invokeCount,
|
|
2047
|
+
concurrency: 1,
|
|
2048
|
+
telemetry: this.telemetry,
|
|
2049
|
+
dispatchDebug: this.dispatchDebug,
|
|
2050
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2051
|
+
});
|
|
2052
|
+
throw new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Runs steps in parallel.
|
|
2056
|
+
*
|
|
2057
|
+
* @param parallelSteps list of lazy steps to run in parallel
|
|
2058
|
+
* @returns results of the functions run in parallel
|
|
2059
|
+
*/
|
|
2060
|
+
async runParallel(parallelSteps) {
|
|
2061
|
+
const initialStepCount = this.stepCount - (parallelSteps.length - 1);
|
|
2062
|
+
const parallelCallState = this.getParallelCallState(parallelSteps.length, initialStepCount);
|
|
2063
|
+
const sortedSteps = sortSteps(this.steps);
|
|
2064
|
+
const plannedParallelStepCount = sortedSteps[initialStepCount + this.planStepCount]?.concurrent;
|
|
2065
|
+
if (parallelCallState !== "first" && plannedParallelStepCount !== parallelSteps.length) {
|
|
2066
|
+
throw new WorkflowError(
|
|
2067
|
+
`Incompatible number of parallel steps when call state was '${parallelCallState}'. Expected ${parallelSteps.length}, got ${plannedParallelStepCount} from the request.`
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
await this.dispatchDebug("onInfo", {
|
|
2071
|
+
info: `Executing parallel steps with: ` + JSON.stringify({
|
|
2072
|
+
parallelCallState,
|
|
2073
|
+
initialStepCount,
|
|
2074
|
+
plannedParallelStepCount,
|
|
2075
|
+
stepCount: this.stepCount,
|
|
2076
|
+
planStepCount: this.planStepCount
|
|
2077
|
+
})
|
|
2078
|
+
});
|
|
2079
|
+
switch (parallelCallState) {
|
|
2080
|
+
case "first": {
|
|
2081
|
+
await submitParallelSteps({
|
|
2082
|
+
context: this.context,
|
|
2083
|
+
steps: parallelSteps,
|
|
2084
|
+
initialStepCount,
|
|
2085
|
+
invokeCount: this.invokeCount,
|
|
2086
|
+
telemetry: this.telemetry,
|
|
2087
|
+
dispatchDebug: this.dispatchDebug
|
|
2088
|
+
});
|
|
2089
|
+
break;
|
|
2090
|
+
}
|
|
2091
|
+
case "partial": {
|
|
2092
|
+
const planStep = this.steps.at(-1);
|
|
2093
|
+
if (!planStep || planStep.targetStep === void 0) {
|
|
2094
|
+
throw new WorkflowError(
|
|
2095
|
+
`There must be a last step and it should have targetStep larger than 0.Received: ${JSON.stringify(planStep)}`
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
const stepIndex = planStep.targetStep - initialStepCount;
|
|
2099
|
+
validateStep(parallelSteps[stepIndex], planStep);
|
|
2100
|
+
try {
|
|
2101
|
+
const parallelStep = parallelSteps[stepIndex];
|
|
2102
|
+
const resultStep = await submitSingleStep({
|
|
2103
|
+
context: this.context,
|
|
2104
|
+
lazyStep: parallelStep,
|
|
2105
|
+
stepId: planStep.targetStep,
|
|
2106
|
+
invokeCount: this.invokeCount,
|
|
2107
|
+
concurrency: parallelSteps.length,
|
|
2108
|
+
telemetry: this.telemetry,
|
|
2109
|
+
dispatchDebug: this.dispatchDebug,
|
|
2110
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2111
|
+
});
|
|
2112
|
+
throw new WorkflowAbort(parallelStep.stepName, resultStep);
|
|
2113
|
+
} catch (error) {
|
|
2114
|
+
if (isInstanceOf(error, WorkflowAbort) || isInstanceOf(error, import_qstash5.QstashError) && error.status === 400) {
|
|
2115
|
+
throw error;
|
|
2116
|
+
}
|
|
2117
|
+
throw new WorkflowError(
|
|
2118
|
+
`Error submitting steps to QStash in partial parallel step execution: ${error}`
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
break;
|
|
2122
|
+
}
|
|
2123
|
+
case "discard": {
|
|
2124
|
+
const resultStep = this.steps.at(-1);
|
|
2125
|
+
const lazyStep = parallelSteps.find(
|
|
2126
|
+
(planStep, index) => resultStep.stepId - index === initialStepCount
|
|
2127
|
+
);
|
|
2128
|
+
if (lazyStep) {
|
|
2129
|
+
await this.dispatchLifecycle("afterExecution", {
|
|
2130
|
+
stepName: lazyStep.stepName,
|
|
2131
|
+
result: lazyStep.parseOut(resultStep)
|
|
2132
|
+
});
|
|
2133
|
+
}
|
|
2134
|
+
throw new WorkflowAbort("discarded parallel");
|
|
2135
|
+
}
|
|
2136
|
+
case "last": {
|
|
2137
|
+
const parallelResultSteps = sortedSteps.filter((step) => step.stepId >= initialStepCount).slice(0, parallelSteps.length);
|
|
2138
|
+
validateParallelSteps(parallelSteps, parallelResultSteps);
|
|
2139
|
+
const isLastMemoized = this.stepCount + 1 === this.nonPlanStepCount && this.steps.at(-1).stepId !== 0;
|
|
2140
|
+
if (isLastMemoized) {
|
|
2141
|
+
const resultStep = this.steps.at(-1);
|
|
2142
|
+
const lazyStep = parallelSteps.find(
|
|
2143
|
+
(planStep, index) => resultStep.stepId - index === initialStepCount
|
|
2144
|
+
);
|
|
2145
|
+
await this.dispatchLifecycle("afterExecution", {
|
|
2146
|
+
stepName: lazyStep.stepName,
|
|
2147
|
+
result: lazyStep.parseOut(resultStep)
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
return parallelResultSteps.map(
|
|
2151
|
+
(step, index) => parallelSteps[index].parseOut(step)
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
const fillValue = void 0;
|
|
2156
|
+
return Array.from({ length: parallelSteps.length }).fill(fillValue);
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Determines the parallel call state
|
|
2160
|
+
*
|
|
2161
|
+
* First filters the steps to get the steps which are after `initialStepCount` parameter.
|
|
2162
|
+
*
|
|
2163
|
+
* Depending on the remaining steps, decides the parallel state:
|
|
2164
|
+
* - "first": If there are no steps
|
|
2165
|
+
* - "last" If there are equal to or more than `2 * parallelStepCount`. We multiply by two
|
|
2166
|
+
* because each step in a parallel execution will have 2 steps: a plan step and a result
|
|
2167
|
+
* step.
|
|
2168
|
+
* - "partial": If the last step is a plan step
|
|
2169
|
+
* - "discard": If the last step is not a plan step. This means that the parallel execution
|
|
2170
|
+
* is in progress (there are still steps to run) and one step has finished and submitted
|
|
2171
|
+
* its result to QStash
|
|
2172
|
+
*
|
|
2173
|
+
* @param parallelStepCount number of steps to run in parallel
|
|
2174
|
+
* @param initialStepCount steps after the parallel invocation
|
|
2175
|
+
* @returns parallel call state
|
|
2176
|
+
*/
|
|
2177
|
+
getParallelCallState(parallelStepCount, initialStepCount) {
|
|
2178
|
+
const remainingSteps = this.steps.filter(
|
|
2179
|
+
(step) => (step.targetStep || step.stepId) >= initialStepCount
|
|
2180
|
+
);
|
|
2181
|
+
if (remainingSteps.length === 0) {
|
|
2182
|
+
return "first";
|
|
2183
|
+
} else if (remainingSteps.length >= 2 * parallelStepCount) {
|
|
2184
|
+
return "last";
|
|
2185
|
+
} else if (remainingSteps.at(-1)?.targetStep) {
|
|
2186
|
+
return "partial";
|
|
2187
|
+
} else {
|
|
2188
|
+
return "discard";
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
/**
|
|
2192
|
+
* Get the promise by executing the lazt steps list. If there is a single
|
|
2193
|
+
* step, we call `runSingle`. Otherwise `runParallel` is called.
|
|
2194
|
+
*
|
|
2195
|
+
* @param lazyStepList steps list to execute
|
|
2196
|
+
* @returns promise corresponding to the execution
|
|
2197
|
+
*/
|
|
2198
|
+
getExecutionPromise(lazyStepList) {
|
|
2199
|
+
return lazyStepList.length === 1 ? this.runSingle(lazyStepList[0]) : this.runParallel(lazyStepList);
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* @param lazyStepList steps we executed
|
|
2203
|
+
* @param result result of the promise from `getExecutionPromise`
|
|
2204
|
+
* @param index index of the current step
|
|
2205
|
+
* @returns result[index] if lazyStepList > 1, otherwise result
|
|
2206
|
+
*/
|
|
2207
|
+
static getResult(lazyStepList, result, index) {
|
|
2208
|
+
if (lazyStepList.length === 1) {
|
|
2209
|
+
return result;
|
|
2210
|
+
} else if (Array.isArray(result) && lazyStepList.length === result.length && index < lazyStepList.length) {
|
|
2211
|
+
return result[index];
|
|
2212
|
+
} else {
|
|
2213
|
+
throw new WorkflowError(
|
|
2214
|
+
`Unexpected parallel call result while executing step ${index}: '${result}'. Expected ${lazyStepList.length} many items`
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
async deferExecution() {
|
|
2219
|
+
await Promise.resolve();
|
|
2220
|
+
await Promise.resolve();
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2223
|
+
var validateStep = (lazyStep, stepFromRequest) => {
|
|
2224
|
+
if (lazyStep.stepName !== stepFromRequest.stepName) {
|
|
2225
|
+
throw new WorkflowError(
|
|
2226
|
+
`Incompatible step name. Expected '${lazyStep.stepName}', got '${stepFromRequest.stepName}' from the request`
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
if (lazyStep.stepType !== stepFromRequest.stepType) {
|
|
2230
|
+
throw new WorkflowError(
|
|
2231
|
+
`Incompatible step type. Expected '${lazyStep.stepType}', got '${stepFromRequest.stepType}' from the request`
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2234
|
+
};
|
|
2235
|
+
var validateParallelSteps = (lazySteps, stepsFromRequest) => {
|
|
2236
|
+
try {
|
|
2237
|
+
for (const [index, stepFromRequest] of stepsFromRequest.entries()) {
|
|
2238
|
+
validateStep(lazySteps[index], stepFromRequest);
|
|
2239
|
+
}
|
|
2240
|
+
} catch (error) {
|
|
2241
|
+
if (isInstanceOf(error, WorkflowError)) {
|
|
2242
|
+
const lazyStepNames = lazySteps.map((lazyStep) => lazyStep.stepName);
|
|
2243
|
+
const lazyStepTypes = lazySteps.map((lazyStep) => lazyStep.stepType);
|
|
2244
|
+
const requestStepNames = stepsFromRequest.map((step) => step.stepName);
|
|
2245
|
+
const requestStepTypes = stepsFromRequest.map((step) => step.stepType);
|
|
2246
|
+
throw new WorkflowError(
|
|
2247
|
+
`Incompatible steps detected in parallel execution: ${error.message}
|
|
2248
|
+
> Step Names from the request: ${JSON.stringify(requestStepNames)}
|
|
2249
|
+
Step Types from the request: ${JSON.stringify(requestStepTypes)}
|
|
2250
|
+
> Step Names expected: ${JSON.stringify(lazyStepNames)}
|
|
2251
|
+
Step Types expected: ${JSON.stringify(lazyStepTypes)}`
|
|
2252
|
+
);
|
|
2253
|
+
}
|
|
2254
|
+
throw error;
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2257
|
+
var sortSteps = (steps) => {
|
|
2258
|
+
const getStepId = (step) => step.targetStep || step.stepId;
|
|
2259
|
+
return [...steps].sort((step, stepOther) => getStepId(step) - getStepId(stepOther));
|
|
2260
|
+
};
|
|
2261
|
+
|
|
2262
|
+
// src/context/api/anthropic.ts
|
|
2263
|
+
var import_qstash6 = require("@upstash/qstash");
|
|
2264
|
+
|
|
2265
|
+
// src/context/provider.ts
|
|
2266
|
+
var getProviderInfo = (api) => {
|
|
2267
|
+
if (!api.provider) {
|
|
2268
|
+
throw new WorkflowError("A Provider must be provided.");
|
|
2269
|
+
}
|
|
2270
|
+
if (api.provider.owner === "upstash") {
|
|
2271
|
+
throw new WorkflowError("Upstash provider isn't supported.");
|
|
2272
|
+
}
|
|
2273
|
+
const { name, provider, ...parameters } = api;
|
|
2274
|
+
if (!provider.baseUrl) throw new TypeError("baseUrl cannot be empty or undefined!");
|
|
2275
|
+
if (!provider.token) throw new TypeError("token cannot be empty or undefined!");
|
|
2276
|
+
if (provider.apiKind !== name) {
|
|
2277
|
+
throw new TypeError(`Unexpected api name. Expected '${provider.apiKind}', received ${name}`);
|
|
2278
|
+
}
|
|
2279
|
+
const providerInfo = {
|
|
2280
|
+
url: provider.getUrl(),
|
|
2281
|
+
baseUrl: provider.baseUrl,
|
|
2282
|
+
route: provider.getRoute(),
|
|
2283
|
+
appendHeaders: provider.getHeaders(parameters),
|
|
2284
|
+
owner: provider.owner,
|
|
2285
|
+
method: provider.method
|
|
2286
|
+
};
|
|
2287
|
+
return provider.onFinish(providerInfo, parameters);
|
|
2288
|
+
};
|
|
2289
|
+
|
|
2290
|
+
// src/context/api/base.ts
|
|
2291
|
+
var BaseWorkflowApi = class {
|
|
2292
|
+
context;
|
|
2293
|
+
/**
|
|
2294
|
+
* @param context workflow context
|
|
2295
|
+
*/
|
|
2296
|
+
constructor({ context }) {
|
|
2297
|
+
this.context = context;
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* context.call which uses a QStash API
|
|
2301
|
+
*
|
|
2302
|
+
* @param stepName name of the step
|
|
2303
|
+
* @param settings call settings including api configuration
|
|
2304
|
+
* @returns call response
|
|
2305
|
+
*/
|
|
2306
|
+
async callApi(stepName, settings) {
|
|
2307
|
+
const { url, appendHeaders, method } = getProviderInfo(settings.api);
|
|
2308
|
+
const { method: userMethod, body, headers = {}, retries = 0, retryDelay, timeout } = settings;
|
|
2309
|
+
return await this.context.call(stepName, {
|
|
2310
|
+
url,
|
|
2311
|
+
method: userMethod ?? method,
|
|
2312
|
+
body: typeof body === "string" ? body : JSON.stringify(body),
|
|
2313
|
+
headers: {
|
|
2314
|
+
...appendHeaders,
|
|
2315
|
+
...headers
|
|
2316
|
+
},
|
|
2317
|
+
retries,
|
|
2318
|
+
retryDelay,
|
|
2319
|
+
timeout
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
};
|
|
2323
|
+
|
|
2324
|
+
// src/context/api/anthropic.ts
|
|
2325
|
+
var AnthropicAPI = class extends BaseWorkflowApi {
|
|
2326
|
+
async call(stepName, settings) {
|
|
2327
|
+
const { token, operation, ...parameters } = settings;
|
|
2328
|
+
return await this.callApi(stepName, {
|
|
2329
|
+
api: {
|
|
2330
|
+
name: "llm",
|
|
2331
|
+
provider: (0, import_qstash6.anthropic)({ token })
|
|
2332
|
+
},
|
|
2333
|
+
...parameters
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
};
|
|
2337
|
+
|
|
2338
|
+
// src/context/api/openai.ts
|
|
2339
|
+
var import_qstash7 = require("@upstash/qstash");
|
|
2340
|
+
var OpenAIAPI = class extends BaseWorkflowApi {
|
|
2341
|
+
async call(stepName, settings) {
|
|
2342
|
+
const { token, organization, operation, baseURL, ...parameters } = settings;
|
|
2343
|
+
const useOpenAI = baseURL === void 0;
|
|
2344
|
+
const provider = useOpenAI ? (0, import_qstash7.openai)({ token, organization }) : (0, import_qstash7.custom)({ baseUrl: baseURL, token });
|
|
2345
|
+
return await this.callApi(stepName, {
|
|
2346
|
+
api: {
|
|
2347
|
+
name: "llm",
|
|
2348
|
+
provider
|
|
2349
|
+
},
|
|
2350
|
+
...parameters
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2354
|
+
|
|
2355
|
+
// src/context/api/resend.ts
|
|
2356
|
+
var import_qstash8 = require("@upstash/qstash");
|
|
2357
|
+
var ResendAPI = class extends BaseWorkflowApi {
|
|
2358
|
+
async call(stepName, settings) {
|
|
2359
|
+
const { token, batch = false, ...parameters } = settings;
|
|
2360
|
+
return await this.callApi(stepName, {
|
|
2361
|
+
api: {
|
|
2362
|
+
name: "email",
|
|
2363
|
+
provider: (0, import_qstash8.resend)({ token, batch })
|
|
2364
|
+
},
|
|
2365
|
+
...parameters
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
};
|
|
2369
|
+
|
|
2370
|
+
// src/context/api/index.ts
|
|
2371
|
+
var WorkflowApi = class extends BaseWorkflowApi {
|
|
2372
|
+
get openai() {
|
|
2373
|
+
return new OpenAIAPI({
|
|
2374
|
+
context: this.context
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
get resend() {
|
|
2378
|
+
return new ResendAPI({
|
|
2379
|
+
context: this.context
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
get anthropic() {
|
|
2383
|
+
return new AnthropicAPI({
|
|
2384
|
+
context: this.context
|
|
2385
|
+
});
|
|
2386
|
+
}
|
|
2387
|
+
};
|
|
2388
|
+
|
|
2389
|
+
// src/serve/serve-many.ts
|
|
2390
|
+
var getWorkflowId = (url) => {
|
|
2391
|
+
const components = url.split("/");
|
|
2392
|
+
const lastComponent = components[components.length - 1];
|
|
2393
|
+
return lastComponent.split("?")[0];
|
|
2394
|
+
};
|
|
2395
|
+
var serveManyBase = ({
|
|
2396
|
+
workflows,
|
|
2397
|
+
getUrl,
|
|
2398
|
+
serveMethod,
|
|
2399
|
+
options
|
|
2400
|
+
}) => {
|
|
2401
|
+
const workflowIds = [];
|
|
2402
|
+
const workflowMap = Object.fromEntries(
|
|
2403
|
+
Object.entries(workflows).map((workflow) => {
|
|
2404
|
+
const workflowId = workflow[0];
|
|
2405
|
+
if (workflowIds.includes(workflowId)) {
|
|
2406
|
+
throw new WorkflowError(
|
|
2407
|
+
`Duplicate workflow name found: '${workflowId}'. Please set different workflow names in serveMany.`
|
|
2408
|
+
);
|
|
2409
|
+
}
|
|
2410
|
+
if (workflowId.includes("/")) {
|
|
2411
|
+
throw new WorkflowError(
|
|
2412
|
+
`Invalid workflow name found: '${workflowId}'. Workflow name cannot contain '/'.`
|
|
2413
|
+
);
|
|
2414
|
+
}
|
|
2415
|
+
workflowIds.push(workflowId);
|
|
2416
|
+
workflow[1].workflowId = workflowId;
|
|
2417
|
+
workflow[1].options = {
|
|
2418
|
+
...options,
|
|
2419
|
+
...workflow[1].options
|
|
2420
|
+
};
|
|
2421
|
+
const params = [workflow[1].routeFunction, workflow[1].options];
|
|
2422
|
+
const handler = serveMethod(...params);
|
|
2423
|
+
return [workflowId, handler];
|
|
2424
|
+
})
|
|
2425
|
+
);
|
|
2426
|
+
return {
|
|
2427
|
+
handler: async (...params) => {
|
|
2428
|
+
const url = getUrl(...params);
|
|
2429
|
+
const pickedWorkflowId = getWorkflowId(url);
|
|
2430
|
+
if (!pickedWorkflowId) {
|
|
2431
|
+
return new Response(
|
|
2432
|
+
`Unexpected request in serveMany. workflowId not set. Please update the URL of your request.`,
|
|
2433
|
+
{
|
|
2434
|
+
status: 404,
|
|
2435
|
+
headers: {
|
|
2436
|
+
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2441
|
+
const workflow = workflowMap[pickedWorkflowId];
|
|
2442
|
+
if (!workflow) {
|
|
2443
|
+
return new Response(
|
|
2444
|
+
`No workflows in serveMany found for '${pickedWorkflowId}'. Please update the URL of your request.`,
|
|
2445
|
+
{
|
|
2446
|
+
status: 404,
|
|
2447
|
+
headers: {
|
|
2448
|
+
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
return await workflow(...params);
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
};
|
|
2457
|
+
var getNewUrlFromWorkflowId = (url, workflowId) => {
|
|
2458
|
+
if (!workflowId) {
|
|
2459
|
+
throw new WorkflowError("You can only call workflow which has a workflowId");
|
|
2460
|
+
}
|
|
2461
|
+
return url.replace(/[^/]+$/, workflowId);
|
|
2462
|
+
};
|
|
2463
|
+
|
|
2464
|
+
// src/middleware/default-callbacks.ts
|
|
2465
|
+
var onErrorWithConsole = async ({ workflowRunId, error }) => {
|
|
2466
|
+
console.error(` [Upstash Workflow]: Error in workflow run ${workflowRunId}: ` + error);
|
|
2467
|
+
};
|
|
2468
|
+
var onWarningWithConsole = async ({ workflowRunId, warning }) => {
|
|
2469
|
+
console.warn(` [Upstash Workflow]: Warning in workflow run ${workflowRunId}: ` + warning);
|
|
2470
|
+
};
|
|
2471
|
+
var onInfoWithConsole = async ({
|
|
2472
|
+
workflowRunId,
|
|
2473
|
+
info
|
|
2474
|
+
}) => {
|
|
2475
|
+
console.info(` [Upstash Workflow]: Info in workflow run ${workflowRunId}: ` + info);
|
|
2476
|
+
};
|
|
2477
|
+
|
|
2478
|
+
// src/middleware/manager.ts
|
|
2479
|
+
var MiddlewareManager = class {
|
|
2480
|
+
middlewares;
|
|
2481
|
+
workflowRunId;
|
|
2482
|
+
context;
|
|
2483
|
+
/**
|
|
2484
|
+
* @param middlewares list of workflow middlewares
|
|
2485
|
+
*/
|
|
2486
|
+
constructor(middlewares = []) {
|
|
2487
|
+
this.middlewares = middlewares;
|
|
2488
|
+
}
|
|
2489
|
+
/**
|
|
2490
|
+
* Assign workflow run ID - will be passed to debug events
|
|
2491
|
+
*
|
|
2492
|
+
* @param workflowRunId workflow run id to assign
|
|
2493
|
+
*/
|
|
2494
|
+
assignWorkflowRunId(workflowRunId) {
|
|
2495
|
+
this.workflowRunId = workflowRunId;
|
|
2496
|
+
}
|
|
2497
|
+
/**
|
|
2498
|
+
* Assign context - required for lifecycle events
|
|
2499
|
+
*
|
|
2500
|
+
* also assigns workflowRunId from context
|
|
2501
|
+
*
|
|
2502
|
+
* @param context workflow context to assign
|
|
2503
|
+
*/
|
|
2504
|
+
assignContext(context) {
|
|
2505
|
+
this.context = context;
|
|
2506
|
+
this.workflowRunId = context.workflowRunId;
|
|
2507
|
+
}
|
|
2508
|
+
/**
|
|
2509
|
+
* Internal method to execute middlewares with common error handling logic
|
|
2510
|
+
*
|
|
2511
|
+
* @param event event name to dispatch
|
|
2512
|
+
* @param params event parameters
|
|
2513
|
+
*/
|
|
2514
|
+
async executeMiddlewares(event, params) {
|
|
2515
|
+
await Promise.all(this.middlewares.map((m) => m.ensureInit()));
|
|
2516
|
+
await Promise.all(
|
|
2517
|
+
this.middlewares.map(async (middleware) => {
|
|
2518
|
+
const callback = middleware.getCallback(event);
|
|
2519
|
+
if (callback) {
|
|
2520
|
+
try {
|
|
2521
|
+
await callback(params);
|
|
2522
|
+
} catch (error) {
|
|
2523
|
+
try {
|
|
2524
|
+
const onErrorCallback = middleware.getCallback("onError") ?? onErrorWithConsole;
|
|
2525
|
+
await onErrorCallback({
|
|
2526
|
+
workflowRunId: this.workflowRunId,
|
|
2527
|
+
error
|
|
2528
|
+
});
|
|
2529
|
+
} catch (onErrorError) {
|
|
2530
|
+
console.error(
|
|
2531
|
+
`Failed while executing "onError" of middleware "${middleware.name}", falling back to logging the error to console. Error: ${onErrorError}`
|
|
2532
|
+
);
|
|
2533
|
+
onErrorWithConsole({
|
|
2534
|
+
workflowRunId: this.workflowRunId,
|
|
2535
|
+
error
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
})
|
|
2541
|
+
);
|
|
2542
|
+
if (event === "onError") {
|
|
2543
|
+
onErrorWithConsole({
|
|
2544
|
+
workflowRunId: this.workflowRunId,
|
|
2545
|
+
...params
|
|
2546
|
+
});
|
|
2547
|
+
} else if (event === "onWarning") {
|
|
2548
|
+
onWarningWithConsole({
|
|
2549
|
+
workflowRunId: this.workflowRunId,
|
|
2550
|
+
...params
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
/**
|
|
2555
|
+
* Dispatch a debug event (onError, onWarning, onInfo)
|
|
2556
|
+
*
|
|
2557
|
+
* @param event debug event name
|
|
2558
|
+
* @param params event parameters
|
|
2559
|
+
*/
|
|
2560
|
+
async dispatchDebug(event, params) {
|
|
2561
|
+
const paramsWithRunId = {
|
|
2562
|
+
...params,
|
|
2563
|
+
workflowRunId: this.workflowRunId
|
|
2564
|
+
};
|
|
2565
|
+
await this.executeMiddlewares(event, paramsWithRunId);
|
|
2566
|
+
}
|
|
2567
|
+
/**
|
|
2568
|
+
* Dispatch a lifecycle event (beforeExecution, afterExecution, runStarted, runCompleted)
|
|
2569
|
+
*
|
|
2570
|
+
* @param event lifecycle event name
|
|
2571
|
+
* @param params event parameters
|
|
2572
|
+
*/
|
|
2573
|
+
async dispatchLifecycle(event, params) {
|
|
2574
|
+
if (!this.context) {
|
|
2575
|
+
throw new WorkflowError(
|
|
2576
|
+
`Something went wrong while calling middlewares. Lifecycle event "${event}" was called before assignContext.`
|
|
2577
|
+
);
|
|
2578
|
+
}
|
|
2579
|
+
const paramsWithContext = {
|
|
2580
|
+
...params,
|
|
2581
|
+
context: this.context
|
|
2582
|
+
};
|
|
2583
|
+
await this.executeMiddlewares(event, paramsWithContext);
|
|
2584
|
+
}
|
|
2585
|
+
};
|
|
2586
|
+
|
|
2587
|
+
// src/context/context.ts
|
|
2588
|
+
var WorkflowContext = class {
|
|
2589
|
+
executor;
|
|
2590
|
+
steps;
|
|
2591
|
+
/**
|
|
2592
|
+
* QStash client of the workflow
|
|
2593
|
+
*
|
|
2594
|
+
* Can be overwritten by passing `qstashClient` parameter in `serve`:
|
|
2595
|
+
*
|
|
2596
|
+
* ```ts
|
|
2597
|
+
* import { Client } from "@upstash/qstash"
|
|
2598
|
+
*
|
|
2599
|
+
* export const POST = serve(
|
|
2600
|
+
* async (context) => {
|
|
2601
|
+
* ...
|
|
2602
|
+
* },
|
|
2603
|
+
* {
|
|
2604
|
+
* qstashClient: new Client({...})
|
|
2605
|
+
* }
|
|
2606
|
+
* )
|
|
2607
|
+
* ```
|
|
2608
|
+
*/
|
|
2609
|
+
qstashClient;
|
|
2610
|
+
/**
|
|
2611
|
+
* Run id of the workflow
|
|
2612
|
+
*/
|
|
2613
|
+
workflowRunId;
|
|
2614
|
+
/**
|
|
2615
|
+
* Creation time of the workflow run
|
|
2616
|
+
*/
|
|
2617
|
+
workflowRunCreatedAt;
|
|
2618
|
+
/**
|
|
2619
|
+
* URL of the workflow
|
|
2620
|
+
*
|
|
2621
|
+
* Can be overwritten by passing a `url` parameter in `serve`:
|
|
2622
|
+
*
|
|
2623
|
+
* ```ts
|
|
2624
|
+
* export const POST = serve(
|
|
2625
|
+
* async (context) => {
|
|
2626
|
+
* ...
|
|
2627
|
+
* },
|
|
2628
|
+
* {
|
|
2629
|
+
* url: "new-url-value"
|
|
2630
|
+
* }
|
|
2631
|
+
* )
|
|
2632
|
+
* ```
|
|
2633
|
+
*/
|
|
2634
|
+
url;
|
|
2635
|
+
/**
|
|
2636
|
+
* Payload of the request which started the workflow.
|
|
2637
|
+
*
|
|
2638
|
+
* To specify its type, you can define `serve` as follows:
|
|
2639
|
+
*
|
|
2640
|
+
* ```ts
|
|
2641
|
+
* // set requestPayload type to MyPayload:
|
|
2642
|
+
* export const POST = serve<MyPayload>(
|
|
2643
|
+
* async (context) => {
|
|
2644
|
+
* ...
|
|
2645
|
+
* }
|
|
2646
|
+
* )
|
|
2647
|
+
* ```
|
|
2648
|
+
*
|
|
2649
|
+
* By default, `serve` tries to apply `JSON.parse` to the request payload.
|
|
2650
|
+
* If your payload is encoded in a format other than JSON, you can utilize
|
|
2651
|
+
* the `initialPayloadParser` parameter:
|
|
2652
|
+
*
|
|
2653
|
+
* ```ts
|
|
2654
|
+
* export const POST = serve<MyPayload>(
|
|
2655
|
+
* async (context) => {
|
|
2656
|
+
* ...
|
|
2657
|
+
* },
|
|
2658
|
+
* {
|
|
2659
|
+
* initialPayloadParser: (initialPayload) => {return doSomething(initialPayload)}
|
|
2660
|
+
* }
|
|
2661
|
+
* )
|
|
2662
|
+
* ```
|
|
2663
|
+
*/
|
|
2664
|
+
requestPayload;
|
|
2665
|
+
/**
|
|
2666
|
+
* headers of the initial request
|
|
2667
|
+
*/
|
|
2668
|
+
headers;
|
|
2669
|
+
/**
|
|
2670
|
+
* Map of environment variables and their values.
|
|
2671
|
+
*
|
|
2672
|
+
* Can be set using the `env` option of serve:
|
|
2673
|
+
*
|
|
2674
|
+
* ```ts
|
|
2675
|
+
* export const POST = serve<MyPayload>(
|
|
2676
|
+
* async (context) => {
|
|
2677
|
+
* const key = context.env["API_KEY"];
|
|
2678
|
+
* },
|
|
2679
|
+
* {
|
|
2680
|
+
* env: {
|
|
2681
|
+
* "API_KEY": "*****";
|
|
2682
|
+
* }
|
|
2683
|
+
* }
|
|
2684
|
+
* )
|
|
2685
|
+
* ```
|
|
2686
|
+
*
|
|
2687
|
+
* Default value is set to `process.env`.
|
|
2688
|
+
*/
|
|
2689
|
+
env;
|
|
2690
|
+
/**
|
|
2691
|
+
* Label to apply to the workflow run.
|
|
2692
|
+
*
|
|
2693
|
+
* Can be used to filter the workflow run logs.
|
|
2694
|
+
*
|
|
2695
|
+
* Can be set by passing a `label` parameter when triggering the workflow
|
|
2696
|
+
* with `client.trigger`:
|
|
2697
|
+
*
|
|
2698
|
+
* ```ts
|
|
2699
|
+
* await client.trigger({
|
|
2700
|
+
* url: "https://workflow-endpoint.com",
|
|
2701
|
+
* label: "my-label"
|
|
2702
|
+
* });
|
|
2703
|
+
* ```
|
|
2704
|
+
*/
|
|
2705
|
+
label;
|
|
2706
|
+
constructor({
|
|
2707
|
+
qstashClient,
|
|
2708
|
+
workflowRunId,
|
|
2709
|
+
workflowRunCreatedAt,
|
|
2710
|
+
headers,
|
|
2711
|
+
steps,
|
|
2712
|
+
url,
|
|
2713
|
+
initialPayload,
|
|
2714
|
+
env,
|
|
2715
|
+
telemetry: telemetry2,
|
|
2716
|
+
invokeCount,
|
|
2717
|
+
label,
|
|
2718
|
+
middlewareManager
|
|
2719
|
+
}) {
|
|
2720
|
+
this.qstashClient = qstashClient;
|
|
2721
|
+
this.workflowRunId = workflowRunId;
|
|
2722
|
+
this.workflowRunCreatedAt = workflowRunCreatedAt;
|
|
2723
|
+
this.steps = steps;
|
|
2724
|
+
this.url = url;
|
|
2725
|
+
this.headers = headers;
|
|
2726
|
+
this.requestPayload = initialPayload;
|
|
2727
|
+
this.env = env ?? {};
|
|
2728
|
+
this.label = label;
|
|
2729
|
+
const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
|
|
2730
|
+
middlewareManagerInstance.assignContext(this);
|
|
2731
|
+
this.executor = new AutoExecutor(
|
|
2732
|
+
this,
|
|
2733
|
+
this.steps,
|
|
2734
|
+
middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
|
|
2735
|
+
middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
|
|
2736
|
+
telemetry2,
|
|
2737
|
+
invokeCount
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
/**
|
|
2741
|
+
* Executes a workflow step
|
|
2742
|
+
*
|
|
2743
|
+
* ```typescript
|
|
2744
|
+
* const result = await context.run("step 1", () => {
|
|
2745
|
+
* return "result"
|
|
2746
|
+
* })
|
|
2747
|
+
* ```
|
|
2748
|
+
*
|
|
2749
|
+
* Can also be called in parallel and the steps will be executed
|
|
2750
|
+
* simulatenously:
|
|
2751
|
+
*
|
|
2752
|
+
* ```typescript
|
|
2753
|
+
* const [result1, result2] = await Promise.all([
|
|
2754
|
+
* context.run("step 1", () => {
|
|
2755
|
+
* return "result1"
|
|
2756
|
+
* }),
|
|
2757
|
+
* context.run("step 2", async () => {
|
|
2758
|
+
* return await fetchResults()
|
|
2759
|
+
* })
|
|
2760
|
+
* ])
|
|
2761
|
+
* ```
|
|
2762
|
+
*
|
|
2763
|
+
* @param stepName name of the step
|
|
2764
|
+
* @param stepFunction step function to be executed
|
|
2765
|
+
* @returns result of the step function
|
|
2766
|
+
*/
|
|
2767
|
+
async run(stepName, stepFunction) {
|
|
2768
|
+
const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
|
|
2769
|
+
return await this.addStep(new LazyFunctionStep(this, stepName, wrappedStepFunction));
|
|
2770
|
+
}
|
|
2771
|
+
/**
|
|
2772
|
+
* Stops the execution for the duration provided.
|
|
2773
|
+
*
|
|
2774
|
+
* ```typescript
|
|
2775
|
+
* await context.sleep('sleep1', 3) // wait for three seconds
|
|
2776
|
+
* ```
|
|
2777
|
+
*
|
|
2778
|
+
* @param stepName
|
|
2779
|
+
* @param duration sleep duration in seconds
|
|
2780
|
+
* @returns undefined
|
|
2781
|
+
*/
|
|
2782
|
+
async sleep(stepName, duration) {
|
|
2783
|
+
await this.addStep(new LazySleepStep(this, stepName, duration));
|
|
2784
|
+
}
|
|
2785
|
+
/**
|
|
2786
|
+
* Stops the execution until the date time provided.
|
|
2787
|
+
*
|
|
2788
|
+
* ```typescript
|
|
2789
|
+
* await context.sleepUntil('sleep1', Date.now() / 1000 + 3) // wait for three seconds
|
|
2790
|
+
* ```
|
|
2791
|
+
*
|
|
2792
|
+
* @param stepName
|
|
2793
|
+
* @param datetime time to sleep until. Can be provided as a number (in unix seconds),
|
|
2794
|
+
* as a Date object or a string (passed to `new Date(datetimeString)`)
|
|
2795
|
+
* @returns undefined
|
|
2796
|
+
*/
|
|
2797
|
+
async sleepUntil(stepName, datetime) {
|
|
2798
|
+
let time;
|
|
2799
|
+
if (typeof datetime === "number") {
|
|
2800
|
+
time = datetime;
|
|
2801
|
+
} else {
|
|
2802
|
+
datetime = typeof datetime === "string" ? new Date(datetime) : datetime;
|
|
2803
|
+
time = Math.round(datetime.getTime() / 1e3);
|
|
2804
|
+
}
|
|
2805
|
+
await this.addStep(new LazySleepUntilStep(this, stepName, time));
|
|
2806
|
+
}
|
|
2807
|
+
async call(stepName, settings) {
|
|
2808
|
+
let callStep;
|
|
2809
|
+
if ("workflow" in settings) {
|
|
2810
|
+
const url = getNewUrlFromWorkflowId(this.url, settings.workflow.workflowId);
|
|
2811
|
+
const stringBody = typeof settings.body === "string" ? settings.body : settings.body === void 0 ? void 0 : JSON.stringify(settings.body);
|
|
2812
|
+
callStep = new LazyCallStep({
|
|
2813
|
+
context: this,
|
|
2814
|
+
stepName,
|
|
2815
|
+
url,
|
|
2816
|
+
method: "POST",
|
|
2817
|
+
body: stringBody,
|
|
2818
|
+
headers: settings.headers || {},
|
|
2819
|
+
retries: settings.retries || 0,
|
|
2820
|
+
retryDelay: settings.retryDelay,
|
|
2821
|
+
timeout: settings.timeout,
|
|
2822
|
+
flowControl: settings.flowControl
|
|
2823
|
+
});
|
|
2824
|
+
} else {
|
|
2825
|
+
callStep = new LazyCallStep({
|
|
2826
|
+
context: this,
|
|
2827
|
+
stepName,
|
|
2828
|
+
url: settings.url,
|
|
2829
|
+
method: settings.method ?? "GET",
|
|
2830
|
+
body: settings.body,
|
|
2831
|
+
headers: settings.headers ?? {},
|
|
2832
|
+
retries: settings.retries ?? 0,
|
|
2833
|
+
retryDelay: settings.retryDelay,
|
|
2834
|
+
timeout: settings.timeout,
|
|
2835
|
+
flowControl: settings.flowControl
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
return await this.addStep(callStep);
|
|
2839
|
+
}
|
|
2840
|
+
/**
|
|
2841
|
+
* Pauses workflow execution until a specific event occurs or a timeout is reached.
|
|
2842
|
+
*
|
|
2843
|
+
*```ts
|
|
2844
|
+
* const result = await workflow.waitForEvent("payment-confirmed", "payment.confirmed", {
|
|
2845
|
+
* timeout: "5m"
|
|
2846
|
+
* });
|
|
2847
|
+
*```
|
|
2848
|
+
*
|
|
2849
|
+
* To notify a waiting workflow:
|
|
2850
|
+
*
|
|
2851
|
+
* ```ts
|
|
2852
|
+
* import { Client } from "@upstash/workflow";
|
|
2853
|
+
*
|
|
2854
|
+
* const client = new Client({ token: "<QSTASH_TOKEN>" });
|
|
2855
|
+
*
|
|
2856
|
+
* await client.notify({
|
|
2857
|
+
* eventId: "payment.confirmed",
|
|
2858
|
+
* data: {
|
|
2859
|
+
* amount: 99.99,
|
|
2860
|
+
* currency: "USD"
|
|
2861
|
+
* }
|
|
2862
|
+
* })
|
|
2863
|
+
* ```
|
|
2864
|
+
*
|
|
2865
|
+
* Alternatively, you can use the `context.notify` method.
|
|
2866
|
+
*
|
|
2867
|
+
* @param stepName
|
|
2868
|
+
* @param eventId - Unique identifier for the event to wait for
|
|
2869
|
+
* @param options - Configuration options.
|
|
2870
|
+
* @returns `{ timeout: boolean, eventData: TEventData }`.
|
|
2871
|
+
* The `timeout` property specifies if the workflow has timed out. The `eventData`
|
|
2872
|
+
* is the data passed when notifying this workflow of an event.
|
|
2873
|
+
*/
|
|
2874
|
+
async waitForEvent(stepName, eventId, options = {}) {
|
|
2875
|
+
const { timeout = "7d" } = options;
|
|
2876
|
+
const timeoutStr = typeof timeout === "string" ? timeout : `${timeout}s`;
|
|
2877
|
+
return await this.addStep(
|
|
2878
|
+
new LazyWaitForEventStep(this, stepName, eventId, timeoutStr)
|
|
2879
|
+
);
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Notify workflow runs waiting for an event
|
|
2883
|
+
*
|
|
2884
|
+
* ```ts
|
|
2885
|
+
* const { eventId, eventData, notifyResponse } = await context.notify(
|
|
2886
|
+
* "notify step", "event-id", "event-data"
|
|
2887
|
+
* );
|
|
2888
|
+
* ```
|
|
2889
|
+
*
|
|
2890
|
+
* Upon `context.notify`, the workflow runs waiting for the given eventId (context.waitForEvent)
|
|
2891
|
+
* will receive the given event data and resume execution.
|
|
2892
|
+
*
|
|
2893
|
+
* The response includes the same eventId and eventData. Additionally, there is
|
|
2894
|
+
* a notifyResponse field which contains a list of `Waiter` objects, each corresponding
|
|
2895
|
+
* to a notified workflow run.
|
|
2896
|
+
*
|
|
2897
|
+
* @param stepName
|
|
2898
|
+
* @param eventId event id to notify
|
|
2899
|
+
* @param eventData event data to notify with
|
|
2900
|
+
* @returns notify response which has event id, event data and list of waiters which were notified
|
|
2901
|
+
*/
|
|
2902
|
+
async notify(stepName, eventId, eventData) {
|
|
2903
|
+
return await this.addStep(
|
|
2904
|
+
new LazyNotifyStep(this, stepName, eventId, eventData, this.qstashClient.http)
|
|
2905
|
+
);
|
|
2906
|
+
}
|
|
2907
|
+
async invoke(stepName, settings) {
|
|
2908
|
+
return await this.addStep(
|
|
2909
|
+
new LazyInvokeStep(this, stepName, settings)
|
|
2910
|
+
);
|
|
2911
|
+
}
|
|
2912
|
+
async createWebhook(stepName) {
|
|
2913
|
+
return await this.addStep(new LazyCreateWebhookStep(this, stepName));
|
|
2914
|
+
}
|
|
2915
|
+
async waitForWebhook(stepName, webhook, timeout) {
|
|
2916
|
+
return await this.addStep(new LazyWaitForWebhookStep(this, stepName, webhook, timeout));
|
|
2917
|
+
}
|
|
2918
|
+
/**
|
|
2919
|
+
* Cancel the current workflow run
|
|
2920
|
+
*
|
|
2921
|
+
* Will throw WorkflowCancelAbort to stop workflow execution.
|
|
2922
|
+
* Shouldn't be inside try/catch.
|
|
2923
|
+
*/
|
|
2924
|
+
async cancel() {
|
|
2925
|
+
throw new WorkflowCancelAbort();
|
|
2926
|
+
}
|
|
2927
|
+
/**
|
|
2928
|
+
* Adds steps to the executor. Needed so that it can be overwritten in
|
|
2929
|
+
* DisabledWorkflowContext.
|
|
2930
|
+
*/
|
|
2931
|
+
async addStep(step) {
|
|
2932
|
+
return await this.executor.addStep(step);
|
|
2933
|
+
}
|
|
2934
|
+
get api() {
|
|
2935
|
+
return new WorkflowApi({
|
|
2936
|
+
context: this
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
};
|
|
2940
|
+
|
|
2941
|
+
// src/serve/authorization.ts
|
|
2942
|
+
var import_qstash9 = require("@upstash/qstash");
|
|
2943
|
+
var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
|
|
2944
|
+
static disabledMessage = "disabled-qstash-worklfow-run";
|
|
2945
|
+
disabled = true;
|
|
2946
|
+
/**
|
|
2947
|
+
* overwrite the WorkflowContext.addStep method to always raise WorkflowAuthError
|
|
2948
|
+
* error in order to stop the execution whenever we encounter a step.
|
|
2949
|
+
*
|
|
2950
|
+
* @param _step
|
|
2951
|
+
*/
|
|
2952
|
+
async addStep(_step) {
|
|
2953
|
+
throw new WorkflowAuthError(_DisabledWorkflowContext.disabledMessage);
|
|
2954
|
+
}
|
|
2955
|
+
/**
|
|
2956
|
+
* overwrite cancel method to throw WorkflowAuthError with the disabledMessage
|
|
2957
|
+
*/
|
|
2958
|
+
async cancel() {
|
|
2959
|
+
throw new WorkflowAuthError(_DisabledWorkflowContext.disabledMessage);
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* copies the passed context to create a DisabledWorkflowContext. Then, runs the
|
|
2963
|
+
* route function with the new context.
|
|
2964
|
+
*
|
|
2965
|
+
* - returns "run-ended" if there are no steps found or
|
|
2966
|
+
* if the auth failed and user called `return`
|
|
2967
|
+
* - returns "step-found" if DisabledWorkflowContext.addStep is called.
|
|
2968
|
+
* - if there is another error, returns the error.
|
|
2969
|
+
*
|
|
2970
|
+
* @param routeFunction
|
|
2971
|
+
*/
|
|
2972
|
+
static async tryAuthentication(routeFunction, context) {
|
|
2973
|
+
const disabledContext = new _DisabledWorkflowContext({
|
|
2974
|
+
qstashClient: new import_qstash9.Client({
|
|
2975
|
+
baseUrl: "disabled-client",
|
|
2976
|
+
token: "disabled-client"
|
|
2977
|
+
}),
|
|
2978
|
+
workflowRunId: context.workflowRunId,
|
|
2979
|
+
workflowRunCreatedAt: context.workflowRunCreatedAt,
|
|
2980
|
+
headers: context.headers,
|
|
2981
|
+
steps: [],
|
|
2982
|
+
url: context.url,
|
|
2983
|
+
initialPayload: context.requestPayload,
|
|
2984
|
+
env: context.env,
|
|
2985
|
+
label: context.label
|
|
2986
|
+
});
|
|
2987
|
+
try {
|
|
2988
|
+
await routeFunction(disabledContext);
|
|
2989
|
+
} catch (error) {
|
|
2990
|
+
if (isInstanceOf(error, WorkflowAuthError) && error.stepName === this.disabledMessage || isInstanceOf(error, WorkflowNonRetryableError) || isInstanceOf(error, WorkflowRetryAfterError)) {
|
|
2991
|
+
return ok("step-found");
|
|
2992
|
+
}
|
|
2993
|
+
console.warn(
|
|
2994
|
+
"Upstash Workflow: Received an error while authorizing request. Please avoid throwing errors before the first step of your workflow."
|
|
2995
|
+
);
|
|
2996
|
+
return err(error);
|
|
2997
|
+
}
|
|
2998
|
+
return ok("run-ended");
|
|
2999
|
+
}
|
|
3000
|
+
};
|
|
3001
|
+
|
|
3002
|
+
// src/workflow-parser.ts
|
|
3003
|
+
var getPayload = async (request) => {
|
|
3004
|
+
try {
|
|
3005
|
+
return await request.text();
|
|
3006
|
+
} catch {
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
var processRawSteps = (rawSteps) => {
|
|
3011
|
+
const [encodedInitialPayload, ...encodedSteps] = rawSteps;
|
|
3012
|
+
const rawInitialPayload = decodeBase64(encodedInitialPayload.body);
|
|
3013
|
+
const initialStep = {
|
|
3014
|
+
stepId: 0,
|
|
3015
|
+
stepName: "init",
|
|
3016
|
+
stepType: "Initial",
|
|
3017
|
+
out: rawInitialPayload,
|
|
3018
|
+
concurrent: NO_CONCURRENCY
|
|
3019
|
+
};
|
|
3020
|
+
const stepsToDecode = encodedSteps.filter((step) => step.callType === "step");
|
|
3021
|
+
const otherSteps = stepsToDecode.map((rawStep) => {
|
|
3022
|
+
const step = JSON.parse(decodeBase64(rawStep.body));
|
|
3023
|
+
return step;
|
|
3024
|
+
});
|
|
3025
|
+
const steps = [initialStep, ...otherSteps];
|
|
3026
|
+
return {
|
|
3027
|
+
rawInitialPayload,
|
|
3028
|
+
steps
|
|
3029
|
+
};
|
|
3030
|
+
};
|
|
3031
|
+
var deduplicateSteps = (steps) => {
|
|
3032
|
+
const targetStepIds = [];
|
|
3033
|
+
const stepIds = [];
|
|
3034
|
+
const deduplicatedSteps = [];
|
|
3035
|
+
for (const step of steps) {
|
|
3036
|
+
if (step.stepId === 0) {
|
|
3037
|
+
if (!targetStepIds.includes(step.targetStep ?? 0)) {
|
|
3038
|
+
deduplicatedSteps.push(step);
|
|
3039
|
+
targetStepIds.push(step.targetStep ?? 0);
|
|
3040
|
+
}
|
|
3041
|
+
} else {
|
|
3042
|
+
if (!stepIds.includes(step.stepId)) {
|
|
3043
|
+
deduplicatedSteps.push(step);
|
|
3044
|
+
stepIds.push(step.stepId);
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
return deduplicatedSteps;
|
|
3049
|
+
};
|
|
3050
|
+
var checkIfLastOneIsDuplicate = async (steps, dispatchDebug) => {
|
|
3051
|
+
if (steps.length < 2) {
|
|
3052
|
+
return false;
|
|
3053
|
+
}
|
|
3054
|
+
const lastStep = steps.at(-1);
|
|
3055
|
+
const lastStepId = lastStep.stepId;
|
|
3056
|
+
const lastTargetStepId = lastStep.targetStep;
|
|
3057
|
+
for (let index = 0; index < steps.length - 1; index++) {
|
|
3058
|
+
const step = steps[index];
|
|
3059
|
+
if (step.stepId === lastStepId && step.targetStep === lastTargetStepId) {
|
|
3060
|
+
const message = `Upstash Workflow: The step '${step.stepName}' with id '${step.stepId}' has run twice during workflow execution. Rest of the workflow will continue running as usual.`;
|
|
3061
|
+
await dispatchDebug?.("onWarning", {
|
|
3062
|
+
warning: message
|
|
3063
|
+
});
|
|
3064
|
+
return true;
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
return false;
|
|
3068
|
+
};
|
|
3069
|
+
var validateRequest = (request) => {
|
|
3070
|
+
if (request.headers.get(WORKFLOW_UNKOWN_SDK_VERSION_HEADER)) {
|
|
3071
|
+
const workflowRunId2 = request.headers.get(WORKFLOW_ID_HEADER);
|
|
3072
|
+
if (!workflowRunId2) {
|
|
3073
|
+
throw new WorkflowError(
|
|
3074
|
+
"Couldn't get workflow id from header when handling unknown sdk request"
|
|
3075
|
+
);
|
|
3076
|
+
}
|
|
3077
|
+
return {
|
|
3078
|
+
unknownSdk: true,
|
|
3079
|
+
isFirstInvocation: true,
|
|
3080
|
+
workflowRunId: workflowRunId2
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
if (request.headers.get(WORKFLOW_FAILURE_CALLBACK_HEADER)) {
|
|
3084
|
+
const workflowRunId2 = request.headers.get(WORKFLOW_ID_HEADER);
|
|
3085
|
+
if (!workflowRunId2) {
|
|
3086
|
+
throw new WorkflowError(
|
|
3087
|
+
"Couldn't get workflow id from header when handling failure callback request"
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3090
|
+
return {
|
|
3091
|
+
unknownSdk: false,
|
|
3092
|
+
isFirstInvocation: true,
|
|
3093
|
+
workflowRunId: workflowRunId2
|
|
3094
|
+
};
|
|
3095
|
+
}
|
|
3096
|
+
const versionHeader = request.headers.get(WORKFLOW_PROTOCOL_VERSION_HEADER);
|
|
3097
|
+
const isFirstInvocation = !versionHeader;
|
|
3098
|
+
if (!isFirstInvocation && versionHeader !== WORKFLOW_PROTOCOL_VERSION) {
|
|
3099
|
+
throw new WorkflowError(
|
|
3100
|
+
`Incompatible workflow sdk protocol version. Expected ${WORKFLOW_PROTOCOL_VERSION}, got ${versionHeader} from the request.`
|
|
3101
|
+
);
|
|
3102
|
+
}
|
|
3103
|
+
const workflowRunId = isFirstInvocation ? getWorkflowRunId() : request.headers.get(WORKFLOW_ID_HEADER) ?? "";
|
|
3104
|
+
if (workflowRunId.length === 0) {
|
|
3105
|
+
throw new WorkflowError("Couldn't get workflow id from header");
|
|
3106
|
+
}
|
|
3107
|
+
return {
|
|
3108
|
+
isFirstInvocation,
|
|
3109
|
+
workflowRunId,
|
|
3110
|
+
unknownSdk: false
|
|
3111
|
+
};
|
|
3112
|
+
};
|
|
3113
|
+
var parseRequest = async ({
|
|
3114
|
+
requestPayload,
|
|
3115
|
+
isFirstInvocation,
|
|
3116
|
+
unknownSdk,
|
|
3117
|
+
workflowRunId,
|
|
3118
|
+
requester,
|
|
3119
|
+
messageId,
|
|
3120
|
+
dispatchDebug
|
|
3121
|
+
}) => {
|
|
3122
|
+
if (isFirstInvocation && !unknownSdk) {
|
|
3123
|
+
return {
|
|
3124
|
+
rawInitialPayload: requestPayload ?? "",
|
|
3125
|
+
steps: [],
|
|
3126
|
+
isLastDuplicate: false,
|
|
3127
|
+
workflowRunEnded: false
|
|
3128
|
+
};
|
|
3129
|
+
} else {
|
|
3130
|
+
let rawSteps;
|
|
3131
|
+
if (!requestPayload) {
|
|
3132
|
+
await dispatchDebug?.("onInfo", {
|
|
3133
|
+
info: "request payload is empty, steps will be fetched from QStash."
|
|
3134
|
+
});
|
|
3135
|
+
const { steps: fetchedSteps, workflowRunEnded } = await getSteps(
|
|
3136
|
+
requester,
|
|
3137
|
+
workflowRunId,
|
|
3138
|
+
messageId,
|
|
3139
|
+
dispatchDebug
|
|
3140
|
+
);
|
|
3141
|
+
if (workflowRunEnded) {
|
|
3142
|
+
return {
|
|
3143
|
+
rawInitialPayload: void 0,
|
|
3144
|
+
steps: void 0,
|
|
3145
|
+
isLastDuplicate: void 0,
|
|
3146
|
+
workflowRunEnded: true
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
rawSteps = fetchedSteps;
|
|
3150
|
+
} else {
|
|
3151
|
+
rawSteps = JSON.parse(requestPayload);
|
|
3152
|
+
}
|
|
3153
|
+
const { rawInitialPayload, steps } = processRawSteps(rawSteps);
|
|
3154
|
+
const isLastDuplicate = await checkIfLastOneIsDuplicate(steps, dispatchDebug);
|
|
3155
|
+
const deduplicatedSteps = deduplicateSteps(steps);
|
|
3156
|
+
return {
|
|
3157
|
+
rawInitialPayload,
|
|
3158
|
+
steps: deduplicatedSteps,
|
|
3159
|
+
isLastDuplicate,
|
|
3160
|
+
workflowRunEnded: false
|
|
3161
|
+
};
|
|
3162
|
+
}
|
|
3163
|
+
};
|
|
3164
|
+
var handleFailure = async ({
|
|
3165
|
+
request,
|
|
3166
|
+
requestPayload,
|
|
3167
|
+
qstashClient,
|
|
3168
|
+
initialPayloadParser,
|
|
3169
|
+
routeFunction,
|
|
3170
|
+
failureFunction,
|
|
3171
|
+
env,
|
|
3172
|
+
dispatchDebug
|
|
3173
|
+
}) => {
|
|
3174
|
+
if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true" && !request.headers.get(WORKFLOW_FAILURE_CALLBACK_HEADER)) {
|
|
3175
|
+
return ok({ result: "not-failure-callback" });
|
|
3176
|
+
}
|
|
3177
|
+
if (!failureFunction) {
|
|
3178
|
+
return ok({ result: "failure-function-undefined" });
|
|
3179
|
+
}
|
|
3180
|
+
try {
|
|
3181
|
+
const { status, header, body, url, sourceBody, workflowRunId, workflowCreatedAt } = JSON.parse(
|
|
3182
|
+
requestPayload
|
|
3183
|
+
);
|
|
3184
|
+
const decodedBody = body ? decodeBase64(body) : "{}";
|
|
3185
|
+
let errorMessage = "";
|
|
3186
|
+
let failStack = "";
|
|
3187
|
+
try {
|
|
3188
|
+
const errorPayload = JSON.parse(decodedBody);
|
|
3189
|
+
if (errorPayload.message) {
|
|
3190
|
+
errorMessage = errorPayload.message;
|
|
3191
|
+
}
|
|
3192
|
+
if (errorPayload.stack) {
|
|
3193
|
+
failStack = errorPayload.stack;
|
|
3194
|
+
}
|
|
3195
|
+
} catch {
|
|
3196
|
+
}
|
|
3197
|
+
if (!errorMessage) {
|
|
3198
|
+
errorMessage = `Couldn't parse 'failResponse' in 'failureFunction', received: '${decodedBody}'`;
|
|
3199
|
+
}
|
|
3200
|
+
const userHeaders = recreateUserHeaders(request.headers);
|
|
3201
|
+
const workflowContext = new WorkflowContext({
|
|
3202
|
+
qstashClient,
|
|
3203
|
+
workflowRunId,
|
|
3204
|
+
initialPayload: sourceBody ? initialPayloadParser(decodeBase64(sourceBody)) : void 0,
|
|
3205
|
+
headers: userHeaders,
|
|
3206
|
+
steps: [],
|
|
3207
|
+
url,
|
|
3208
|
+
env,
|
|
3209
|
+
telemetry: void 0,
|
|
3210
|
+
// not going to make requests in authentication check
|
|
3211
|
+
label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
|
|
3212
|
+
workflowRunCreatedAt: workflowCreatedAt,
|
|
3213
|
+
middlewareManager: void 0
|
|
3214
|
+
});
|
|
3215
|
+
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3216
|
+
routeFunction,
|
|
3217
|
+
workflowContext
|
|
3218
|
+
);
|
|
3219
|
+
if (authCheck.isErr()) {
|
|
3220
|
+
await dispatchDebug?.("onError", {
|
|
3221
|
+
error: authCheck.error
|
|
3222
|
+
});
|
|
3223
|
+
return err(authCheck.error);
|
|
3224
|
+
} else if (authCheck.value === "run-ended") {
|
|
3225
|
+
return err(new WorkflowError("Not authorized to run the failure function."));
|
|
3226
|
+
}
|
|
3227
|
+
const failureResponse = await failureFunction({
|
|
3228
|
+
context: workflowContext,
|
|
3229
|
+
failStatus: status,
|
|
3230
|
+
failResponse: errorMessage,
|
|
3231
|
+
failHeaders: header,
|
|
3232
|
+
failStack
|
|
3233
|
+
});
|
|
3234
|
+
return ok({ result: "failure-function-executed", response: failureResponse });
|
|
3235
|
+
} catch (error) {
|
|
3236
|
+
return err(error);
|
|
3237
|
+
}
|
|
3238
|
+
};
|
|
3239
|
+
|
|
3240
|
+
// src/serve/multi-region/handlers.ts
|
|
3241
|
+
var import_qstash10 = require("@upstash/qstash");
|
|
3242
|
+
|
|
3243
|
+
// src/serve/multi-region/utils.ts
|
|
3244
|
+
var VALID_REGIONS = ["EU_CENTRAL_1", "US_EAST_1"];
|
|
3245
|
+
var getRegionFromEnvironment = (environment) => {
|
|
3246
|
+
const region = environment.QSTASH_REGION;
|
|
3247
|
+
return normalizeRegionHeader(region);
|
|
3248
|
+
};
|
|
3249
|
+
function readEnvironmentVariables(environmentVariables, environment, region) {
|
|
3250
|
+
const result = {};
|
|
3251
|
+
for (const variable of environmentVariables) {
|
|
3252
|
+
const key = region ? `${region}_${variable}` : variable;
|
|
3253
|
+
result[variable] = environment[key];
|
|
3254
|
+
}
|
|
3255
|
+
return result;
|
|
3256
|
+
}
|
|
3257
|
+
function readClientEnvironmentVariables(environment, region) {
|
|
3258
|
+
return readEnvironmentVariables(["QSTASH_URL", "QSTASH_TOKEN"], environment, region);
|
|
3259
|
+
}
|
|
3260
|
+
function readReceiverEnvironmentVariables(environment, region) {
|
|
3261
|
+
return readEnvironmentVariables(
|
|
3262
|
+
["QSTASH_CURRENT_SIGNING_KEY", "QSTASH_NEXT_SIGNING_KEY"],
|
|
3263
|
+
environment,
|
|
3264
|
+
region
|
|
3265
|
+
);
|
|
3266
|
+
}
|
|
3267
|
+
function normalizeRegionHeader(region) {
|
|
3268
|
+
if (!region) {
|
|
3269
|
+
return void 0;
|
|
3270
|
+
}
|
|
3271
|
+
region = region.replaceAll("-", "_").toUpperCase();
|
|
3272
|
+
if (VALID_REGIONS.includes(region)) {
|
|
3273
|
+
return region;
|
|
3274
|
+
}
|
|
3275
|
+
console.warn(
|
|
3276
|
+
`[Upstash Workflow] Invalid UPSTASH-REGION header value: "${region}". Expected one of: ${VALID_REGIONS.join(
|
|
3277
|
+
", "
|
|
3278
|
+
)}.`
|
|
3279
|
+
);
|
|
3280
|
+
return void 0;
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
// src/serve/multi-region/handlers.ts
|
|
3284
|
+
var getHandlersForRequest = (qstashHandlers, regionHeader, isFirstInvocation) => {
|
|
3285
|
+
if (qstashHandlers.mode === "single-region") {
|
|
3286
|
+
return qstashHandlers.handlers;
|
|
3287
|
+
}
|
|
3288
|
+
let targetRegion;
|
|
3289
|
+
if (isFirstInvocation && !regionHeader) {
|
|
3290
|
+
targetRegion = qstashHandlers.defaultRegion;
|
|
3291
|
+
} else {
|
|
3292
|
+
const normalizedRegion = regionHeader ? normalizeRegionHeader(regionHeader) : void 0;
|
|
3293
|
+
targetRegion = normalizedRegion ?? qstashHandlers.defaultRegion;
|
|
3294
|
+
}
|
|
3295
|
+
const handler = qstashHandlers.handlers[targetRegion];
|
|
3296
|
+
if (!handler) {
|
|
3297
|
+
console.warn(
|
|
3298
|
+
`[Upstash Workflow] No handler found for region "${targetRegion}". Falling back to default region.`
|
|
3299
|
+
);
|
|
3300
|
+
return qstashHandlers.handlers[qstashHandlers.defaultRegion];
|
|
3301
|
+
}
|
|
3302
|
+
return handler;
|
|
3303
|
+
};
|
|
3304
|
+
var createRegionalHandler = (environment, receiverConfig, region, clientOptions) => {
|
|
3305
|
+
const clientEnv = readClientEnvironmentVariables(environment, region);
|
|
3306
|
+
const client = new import_qstash10.Client({
|
|
3307
|
+
...clientOptions,
|
|
3308
|
+
baseUrl: clientEnv.QSTASH_URL,
|
|
3309
|
+
token: clientEnv.QSTASH_TOKEN
|
|
3310
|
+
});
|
|
3311
|
+
const receiver = getReceiver(environment, receiverConfig, region);
|
|
3312
|
+
return { client, receiver };
|
|
3313
|
+
};
|
|
3314
|
+
var shouldUseMultiRegionMode = (environment, qstashClientOption) => {
|
|
3315
|
+
const hasRegionEnv = Boolean(getRegionFromEnvironment(environment));
|
|
3316
|
+
if (hasRegionEnv && (!qstashClientOption || !("http" in qstashClientOption))) {
|
|
3317
|
+
return {
|
|
3318
|
+
isMultiRegion: true,
|
|
3319
|
+
defaultRegion: getRegionFromEnvironment(environment),
|
|
3320
|
+
clientOptions: qstashClientOption
|
|
3321
|
+
};
|
|
3322
|
+
} else {
|
|
3323
|
+
return { isMultiRegion: false };
|
|
3324
|
+
}
|
|
3325
|
+
};
|
|
3326
|
+
var getQStashHandlers = ({
|
|
3327
|
+
environment,
|
|
3328
|
+
qstashClientOption,
|
|
3329
|
+
receiverConfig
|
|
3330
|
+
}) => {
|
|
3331
|
+
const multiRegion = shouldUseMultiRegionMode(environment, qstashClientOption);
|
|
3332
|
+
if (multiRegion.isMultiRegion) {
|
|
3333
|
+
const regions = ["US_EAST_1", "EU_CENTRAL_1"];
|
|
3334
|
+
const handlers = {};
|
|
3335
|
+
for (const region of regions) {
|
|
3336
|
+
try {
|
|
3337
|
+
handlers[region] = createRegionalHandler(
|
|
3338
|
+
environment,
|
|
3339
|
+
receiverConfig,
|
|
3340
|
+
region,
|
|
3341
|
+
multiRegion.clientOptions
|
|
3342
|
+
);
|
|
3343
|
+
} catch (error) {
|
|
3344
|
+
console.warn(`[Upstash Workflow] Failed to create handler for region ${region}:`, error);
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
return {
|
|
3348
|
+
mode: "multi-region",
|
|
3349
|
+
handlers,
|
|
3350
|
+
defaultRegion: multiRegion.defaultRegion
|
|
3351
|
+
};
|
|
3352
|
+
} else {
|
|
3353
|
+
return {
|
|
3354
|
+
mode: "single-region",
|
|
3355
|
+
handlers: {
|
|
3356
|
+
client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash10.Client({
|
|
3357
|
+
...qstashClientOption,
|
|
3358
|
+
baseUrl: environment.QSTASH_URL,
|
|
3359
|
+
token: environment.QSTASH_TOKEN
|
|
3360
|
+
}),
|
|
3361
|
+
receiver: getReceiver(environment, receiverConfig)
|
|
3362
|
+
}
|
|
3363
|
+
};
|
|
3364
|
+
}
|
|
3365
|
+
};
|
|
3366
|
+
var getReceiver = (environment, receiverConfig, region) => {
|
|
3367
|
+
if (typeof receiverConfig === "string") {
|
|
3368
|
+
if (receiverConfig === "set-to-undefined") {
|
|
3369
|
+
return void 0;
|
|
3370
|
+
}
|
|
3371
|
+
const receiverEnv = readReceiverEnvironmentVariables(environment, region);
|
|
3372
|
+
return receiverEnv.QSTASH_CURRENT_SIGNING_KEY && receiverEnv.QSTASH_NEXT_SIGNING_KEY ? new import_qstash10.Receiver({
|
|
3373
|
+
currentSigningKey: receiverEnv.QSTASH_CURRENT_SIGNING_KEY,
|
|
3374
|
+
nextSigningKey: receiverEnv.QSTASH_NEXT_SIGNING_KEY
|
|
3375
|
+
}) : void 0;
|
|
3376
|
+
} else {
|
|
3377
|
+
return receiverConfig;
|
|
3378
|
+
}
|
|
3379
|
+
};
|
|
3380
|
+
var getQStashHandlerOptions = (...params) => {
|
|
3381
|
+
const handlers = getQStashHandlers(...params);
|
|
3382
|
+
return {
|
|
3383
|
+
qstashHandlers: handlers,
|
|
3384
|
+
defaultReceiver: handlers.mode === "single-region" ? handlers.handlers.receiver : handlers.handlers[handlers.defaultRegion].receiver,
|
|
3385
|
+
defaultClient: handlers.mode === "single-region" ? handlers.handlers.client : handlers.handlers[handlers.defaultRegion].client
|
|
3386
|
+
};
|
|
3387
|
+
};
|
|
3388
|
+
|
|
3389
|
+
// src/middleware/middleware.ts
|
|
3390
|
+
var WorkflowMiddleware = class {
|
|
3391
|
+
name;
|
|
3392
|
+
initCallbacks;
|
|
3393
|
+
/**
|
|
3394
|
+
* Callback functions
|
|
3395
|
+
*
|
|
3396
|
+
* Initially set to undefined, will be populated after init is called
|
|
3397
|
+
*/
|
|
3398
|
+
middlewareCallbacks = void 0;
|
|
3399
|
+
constructor(parameters) {
|
|
3400
|
+
this.name = parameters.name;
|
|
3401
|
+
if ("init" in parameters) {
|
|
3402
|
+
this.initCallbacks = parameters.init;
|
|
3403
|
+
} else {
|
|
3404
|
+
this.middlewareCallbacks = parameters.callbacks;
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
async ensureInit() {
|
|
3408
|
+
if (!this.middlewareCallbacks) {
|
|
3409
|
+
if (!this.initCallbacks) {
|
|
3410
|
+
throw new WorkflowError(`Middleware "${this.name}" has no callbacks or init defined.`);
|
|
3411
|
+
}
|
|
3412
|
+
this.middlewareCallbacks = await this.initCallbacks();
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
/**
|
|
3416
|
+
* Gets a callback function by name.
|
|
3417
|
+
*
|
|
3418
|
+
* @param callback name of the callback to retrieve
|
|
3419
|
+
*/
|
|
3420
|
+
getCallback(callback) {
|
|
3421
|
+
return this.middlewareCallbacks?.[callback];
|
|
3422
|
+
}
|
|
3423
|
+
};
|
|
3424
|
+
|
|
3425
|
+
// src/middleware/logging.ts
|
|
3426
|
+
var loggingMiddleware = new WorkflowMiddleware({
|
|
3427
|
+
name: "logging",
|
|
3428
|
+
callbacks: {
|
|
3429
|
+
afterExecution(params) {
|
|
3430
|
+
const { context, ...rest } = params;
|
|
3431
|
+
console.log(" [Upstash Workflow]: Step executed:", {
|
|
3432
|
+
workflowRunId: context.workflowRunId,
|
|
3433
|
+
...rest
|
|
3434
|
+
});
|
|
3435
|
+
},
|
|
3436
|
+
beforeExecution(params) {
|
|
3437
|
+
const { context, ...rest } = params;
|
|
3438
|
+
console.log(" [Upstash Workflow]: Step execution started:", {
|
|
3439
|
+
workflowRunId: context.workflowRunId,
|
|
3440
|
+
...rest
|
|
3441
|
+
});
|
|
3442
|
+
},
|
|
3443
|
+
runStarted(params) {
|
|
3444
|
+
const { context, ...rest } = params;
|
|
3445
|
+
console.log(" [Upstash Workflow]: Workflow run started:", {
|
|
3446
|
+
workflowRunId: context.workflowRunId,
|
|
3447
|
+
...rest
|
|
3448
|
+
});
|
|
3449
|
+
},
|
|
3450
|
+
runCompleted(params) {
|
|
3451
|
+
const { context, ...rest } = params;
|
|
3452
|
+
console.log(" [Upstash Workflow]: Workflow run completed:", {
|
|
3453
|
+
workflowRunId: context.workflowRunId,
|
|
3454
|
+
...rest
|
|
3455
|
+
});
|
|
3456
|
+
},
|
|
3457
|
+
onError: onErrorWithConsole,
|
|
3458
|
+
onWarning: onWarningWithConsole,
|
|
3459
|
+
onInfo: onInfoWithConsole
|
|
3460
|
+
}
|
|
3461
|
+
});
|
|
3462
|
+
|
|
3463
|
+
// src/serve/options.ts
|
|
3464
|
+
var createResponseData = (workflowRunId, detailedFinishCondition) => {
|
|
3465
|
+
const baseHeaders = {
|
|
3466
|
+
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
3467
|
+
"Upstash-workflow-sdk": VERSION
|
|
3468
|
+
};
|
|
3469
|
+
if (detailedFinishCondition?.condition === "auth-fail") {
|
|
3470
|
+
return {
|
|
3471
|
+
text: JSON.stringify({
|
|
3472
|
+
message: AUTH_FAIL_MESSAGE,
|
|
3473
|
+
workflowRunId
|
|
3474
|
+
}),
|
|
3475
|
+
status: 400,
|
|
3476
|
+
headers: baseHeaders
|
|
3477
|
+
};
|
|
3478
|
+
} else if (detailedFinishCondition?.condition === "non-retryable-error") {
|
|
3479
|
+
return {
|
|
3480
|
+
text: JSON.stringify(formatWorkflowError(detailedFinishCondition.result)),
|
|
3481
|
+
status: 489,
|
|
3482
|
+
headers: {
|
|
3483
|
+
...baseHeaders,
|
|
3484
|
+
"Upstash-NonRetryable-Error": "true"
|
|
3485
|
+
}
|
|
3486
|
+
};
|
|
3487
|
+
} else if (detailedFinishCondition?.condition === "retry-after-error") {
|
|
3488
|
+
return {
|
|
3489
|
+
text: JSON.stringify(formatWorkflowError(detailedFinishCondition.result)),
|
|
3490
|
+
status: 429,
|
|
3491
|
+
headers: {
|
|
3492
|
+
...baseHeaders,
|
|
3493
|
+
"Retry-After": detailedFinishCondition.result.retryAfter.toString()
|
|
3494
|
+
}
|
|
3495
|
+
};
|
|
3496
|
+
} else if (detailedFinishCondition?.condition === "failure-callback-executed") {
|
|
3497
|
+
return {
|
|
3498
|
+
text: JSON.stringify({ result: detailedFinishCondition.result ?? void 0 }),
|
|
3499
|
+
status: 200,
|
|
3500
|
+
headers: baseHeaders
|
|
3501
|
+
};
|
|
3502
|
+
} else if (detailedFinishCondition?.condition === "failure-callback-undefined") {
|
|
3503
|
+
return {
|
|
3504
|
+
text: JSON.stringify({
|
|
3505
|
+
workflowRunId,
|
|
3506
|
+
finishCondition: detailedFinishCondition.condition
|
|
3507
|
+
}),
|
|
3508
|
+
status: 200,
|
|
3509
|
+
headers: {
|
|
3510
|
+
...baseHeaders,
|
|
3511
|
+
"Upstash-Workflow-Failure-Callback-Notfound": "true"
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
return {
|
|
3516
|
+
text: JSON.stringify({
|
|
3517
|
+
workflowRunId,
|
|
3518
|
+
finishCondition: detailedFinishCondition.condition
|
|
3519
|
+
}),
|
|
3520
|
+
status: 200,
|
|
3521
|
+
headers: baseHeaders
|
|
3522
|
+
};
|
|
3523
|
+
};
|
|
3524
|
+
var processOptions = (options, internalOptions) => {
|
|
3525
|
+
const environment = options?.env ?? (typeof process === "undefined" ? {} : process.env);
|
|
3526
|
+
const {
|
|
3527
|
+
qstashHandlers,
|
|
3528
|
+
defaultClient: qstashClient,
|
|
3529
|
+
defaultReceiver: receiver
|
|
3530
|
+
} = getQStashHandlerOptions({
|
|
3531
|
+
environment,
|
|
3532
|
+
qstashClientOption: options?.qstashClient,
|
|
3533
|
+
receiverConfig: options && "receiver" in options ? options.receiver ? options.receiver : "set-to-undefined" : "not-set"
|
|
3534
|
+
});
|
|
3535
|
+
return {
|
|
3536
|
+
qstashClient,
|
|
3537
|
+
initialPayloadParser: (initialRequest) => {
|
|
3538
|
+
if (!initialRequest) {
|
|
3539
|
+
return void 0;
|
|
3540
|
+
}
|
|
3541
|
+
try {
|
|
3542
|
+
const parsed = JSON.parse(initialRequest);
|
|
3543
|
+
return options?.schema ? options.schema.parse(parsed) : parsed;
|
|
3544
|
+
} catch (error) {
|
|
3545
|
+
if (error instanceof SyntaxError) {
|
|
3546
|
+
return initialRequest;
|
|
3547
|
+
}
|
|
3548
|
+
throw error;
|
|
3549
|
+
}
|
|
3550
|
+
},
|
|
3551
|
+
receiver,
|
|
3552
|
+
baseUrl: environment.UPSTASH_WORKFLOW_URL,
|
|
3553
|
+
env: environment,
|
|
3554
|
+
disableTelemetry: false,
|
|
3555
|
+
...options,
|
|
3556
|
+
// merge middlewares
|
|
3557
|
+
middlewares: [options?.middlewares ?? [], options?.verbose ? [loggingMiddleware] : []].flat(),
|
|
3558
|
+
internal: {
|
|
3559
|
+
generateResponse: internalOptions?.generateResponse ?? ((responseData) => {
|
|
3560
|
+
return new Response(responseData.text, {
|
|
3561
|
+
status: responseData.status,
|
|
3562
|
+
headers: responseData.headers
|
|
3563
|
+
});
|
|
3564
|
+
}),
|
|
3565
|
+
useJSONContent: internalOptions?.useJSONContent ?? false,
|
|
3566
|
+
qstashHandlers
|
|
3567
|
+
}
|
|
3568
|
+
};
|
|
3569
|
+
};
|
|
3570
|
+
var determineUrls = async (request, url, baseUrl, dispatchDebug) => {
|
|
3571
|
+
const initialWorkflowUrl = url ?? request.url;
|
|
3572
|
+
const workflowUrl = baseUrl ? initialWorkflowUrl.replace(/^(https?:\/\/[^/]+)(\/.*)?$/, (_, matchedBaseUrl, path) => {
|
|
3573
|
+
return baseUrl + (path || "");
|
|
3574
|
+
}) : initialWorkflowUrl;
|
|
3575
|
+
if (workflowUrl !== initialWorkflowUrl) {
|
|
3576
|
+
await dispatchDebug("onInfo", {
|
|
3577
|
+
info: `The workflow URL's base URL has been replaced with the provided baseUrl. Original URL: ${initialWorkflowUrl}, New URL: ${workflowUrl}`
|
|
3578
|
+
});
|
|
3579
|
+
}
|
|
3580
|
+
if (workflowUrl.includes("localhost")) {
|
|
3581
|
+
await dispatchDebug("onInfo", {
|
|
3582
|
+
info: `Workflow URL contains localhost. This can happen in local development, but shouldn't happen in production unless you have a route which contains localhost. Received: ${workflowUrl}`
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
if (!(workflowUrl.startsWith("http://") || workflowUrl.startsWith("https://"))) {
|
|
3586
|
+
throw new WorkflowError(
|
|
3587
|
+
`Workflow URL should start with 'http://' or 'https://'. Recevied is '${workflowUrl}'`
|
|
3588
|
+
);
|
|
3589
|
+
}
|
|
3590
|
+
return {
|
|
3591
|
+
workflowUrl
|
|
3592
|
+
};
|
|
3593
|
+
};
|
|
3594
|
+
var AUTH_FAIL_MESSAGE = `Failed to authenticate Workflow request. If this is unexpected, see the caveat https://upstash.com/docs/workflow/basics/caveats#avoid-non-deterministic-code-outside-context-run`;
|
|
3595
|
+
|
|
3596
|
+
// src/serve/index.ts
|
|
3597
|
+
var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
|
|
3598
|
+
const {
|
|
3599
|
+
initialPayloadParser,
|
|
3600
|
+
url,
|
|
3601
|
+
failureFunction,
|
|
3602
|
+
baseUrl,
|
|
3603
|
+
env,
|
|
3604
|
+
disableTelemetry,
|
|
3605
|
+
middlewares,
|
|
3606
|
+
internal
|
|
3607
|
+
} = processOptions(options, internalOptions);
|
|
3608
|
+
telemetry2 = disableTelemetry ? void 0 : telemetry2;
|
|
3609
|
+
const { generateResponse: responseGenerator, useJSONContent } = internal;
|
|
3610
|
+
const handler = async (request, middlewareManager) => {
|
|
3611
|
+
await middlewareManager.dispatchDebug("onInfo", {
|
|
3612
|
+
info: `Received request for workflow execution.`
|
|
3613
|
+
});
|
|
3614
|
+
const { workflowUrl } = await determineUrls(
|
|
3615
|
+
request,
|
|
3616
|
+
url,
|
|
3617
|
+
baseUrl,
|
|
3618
|
+
middlewareManager.dispatchDebug.bind(middlewareManager)
|
|
3619
|
+
);
|
|
3620
|
+
const { isFirstInvocation, workflowRunId, unknownSdk } = validateRequest(request);
|
|
3621
|
+
const regionHeader = request.headers.get("upstash-region");
|
|
3622
|
+
const { client: regionalClient, receiver: regionalReceiver } = getHandlersForRequest(
|
|
3623
|
+
internal.qstashHandlers,
|
|
3624
|
+
regionHeader,
|
|
3625
|
+
isFirstInvocation
|
|
3626
|
+
);
|
|
3627
|
+
const requestPayload = await getPayload(request) ?? "";
|
|
3628
|
+
await verifyRequest(requestPayload, request.headers.get("upstash-signature"), regionalReceiver);
|
|
3629
|
+
middlewareManager.assignWorkflowRunId(workflowRunId);
|
|
3630
|
+
await middlewareManager.dispatchDebug("onInfo", {
|
|
3631
|
+
info: `Run id identified. isFirstInvocation: ${isFirstInvocation}, unknownSdk: ${unknownSdk}`
|
|
3632
|
+
});
|
|
3633
|
+
const { rawInitialPayload, steps, isLastDuplicate, workflowRunEnded } = await parseRequest({
|
|
3634
|
+
requestPayload,
|
|
3635
|
+
isFirstInvocation,
|
|
3636
|
+
unknownSdk,
|
|
3637
|
+
workflowRunId,
|
|
3638
|
+
requester: regionalClient.http,
|
|
3639
|
+
messageId: request.headers.get("upstash-message-id"),
|
|
3640
|
+
dispatchDebug: middlewareManager.dispatchDebug.bind(middlewareManager)
|
|
3641
|
+
});
|
|
3642
|
+
if (workflowRunEnded) {
|
|
3643
|
+
return responseGenerator(
|
|
3644
|
+
createResponseData(workflowRunId, {
|
|
3645
|
+
condition: "workflow-already-ended"
|
|
3646
|
+
})
|
|
3647
|
+
);
|
|
3648
|
+
}
|
|
3649
|
+
if (isLastDuplicate) {
|
|
3650
|
+
return responseGenerator(
|
|
3651
|
+
createResponseData(workflowRunId, {
|
|
3652
|
+
condition: "duplicate-step"
|
|
3653
|
+
})
|
|
3654
|
+
);
|
|
3655
|
+
}
|
|
3656
|
+
const failureCheck = await handleFailure({
|
|
3657
|
+
request,
|
|
3658
|
+
requestPayload,
|
|
3659
|
+
qstashClient: regionalClient,
|
|
3660
|
+
initialPayloadParser,
|
|
3661
|
+
routeFunction,
|
|
3662
|
+
failureFunction,
|
|
3663
|
+
env,
|
|
3664
|
+
dispatchDebug: middlewareManager.dispatchDebug.bind(middlewareManager)
|
|
3665
|
+
});
|
|
3666
|
+
if (failureCheck.isErr()) {
|
|
3667
|
+
throw failureCheck.error;
|
|
3668
|
+
} else if (failureCheck.value.result === "failure-function-executed") {
|
|
3669
|
+
await middlewareManager.dispatchDebug("onInfo", {
|
|
3670
|
+
info: `Handled failure callback.`
|
|
3671
|
+
});
|
|
3672
|
+
return responseGenerator(
|
|
3673
|
+
createResponseData(workflowRunId, {
|
|
3674
|
+
condition: "failure-callback-executed",
|
|
3675
|
+
result: failureCheck.value.response
|
|
3676
|
+
})
|
|
3677
|
+
);
|
|
3678
|
+
} else if (failureCheck.value.result === "failure-function-undefined") {
|
|
3679
|
+
await middlewareManager.dispatchDebug("onInfo", {
|
|
3680
|
+
info: `Failure callback invoked but no failure function defined.`
|
|
3681
|
+
});
|
|
3682
|
+
return responseGenerator(
|
|
3683
|
+
createResponseData(workflowRunId, {
|
|
3684
|
+
condition: "failure-callback-undefined"
|
|
3685
|
+
})
|
|
3686
|
+
);
|
|
3687
|
+
}
|
|
3688
|
+
const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
|
|
3689
|
+
const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
|
|
3690
|
+
const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
3691
|
+
const workflowContext = new WorkflowContext({
|
|
3692
|
+
qstashClient: regionalClient,
|
|
3693
|
+
workflowRunId,
|
|
3694
|
+
initialPayload: initialPayloadParser(rawInitialPayload),
|
|
3695
|
+
headers: recreateUserHeaders(request.headers),
|
|
3696
|
+
steps,
|
|
3697
|
+
url: workflowUrl,
|
|
3698
|
+
env,
|
|
3699
|
+
telemetry: telemetry2,
|
|
3700
|
+
invokeCount,
|
|
3701
|
+
label,
|
|
3702
|
+
workflowRunCreatedAt: Number(workflowRunCreatedAt),
|
|
3703
|
+
middlewareManager
|
|
3704
|
+
});
|
|
3705
|
+
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3706
|
+
routeFunction,
|
|
3707
|
+
workflowContext
|
|
3708
|
+
);
|
|
3709
|
+
if (authCheck.isErr()) {
|
|
3710
|
+
throw authCheck.error;
|
|
3711
|
+
} else if (authCheck.value === "run-ended") {
|
|
3712
|
+
await middlewareManager.dispatchDebug("onError", {
|
|
3713
|
+
error: new Error(AUTH_FAIL_MESSAGE)
|
|
3714
|
+
});
|
|
3715
|
+
return responseGenerator(
|
|
3716
|
+
createResponseData(isFirstInvocation ? "no-workflow-id" : workflowContext.workflowRunId, {
|
|
3717
|
+
condition: "auth-fail"
|
|
3718
|
+
})
|
|
3719
|
+
);
|
|
3720
|
+
}
|
|
3721
|
+
const callReturnCheck = await handleThirdPartyCallResult({
|
|
3722
|
+
request,
|
|
3723
|
+
requestPayload: rawInitialPayload,
|
|
3724
|
+
client: regionalClient,
|
|
3725
|
+
workflowUrl,
|
|
3726
|
+
telemetry: telemetry2,
|
|
3727
|
+
middlewareManager
|
|
3728
|
+
});
|
|
3729
|
+
if (callReturnCheck.isErr()) {
|
|
3730
|
+
throw callReturnCheck.error;
|
|
3731
|
+
} else if (callReturnCheck.value === "continue-workflow") {
|
|
3732
|
+
const result = isFirstInvocation ? await triggerFirstInvocation({
|
|
3733
|
+
workflowContext,
|
|
3734
|
+
useJSONContent,
|
|
3735
|
+
telemetry: telemetry2,
|
|
3736
|
+
invokeCount,
|
|
3737
|
+
middlewareManager,
|
|
3738
|
+
unknownSdk
|
|
3739
|
+
}) : await triggerRouteFunction({
|
|
3740
|
+
onStep: async () => {
|
|
3741
|
+
if (steps.length === 1) {
|
|
3742
|
+
await middlewareManager.dispatchLifecycle("runStarted", {});
|
|
3743
|
+
}
|
|
3744
|
+
return await routeFunction(workflowContext);
|
|
3745
|
+
},
|
|
3746
|
+
onCleanup: async (result2) => {
|
|
3747
|
+
await middlewareManager.dispatchLifecycle("runCompleted", {
|
|
3748
|
+
result: result2
|
|
3749
|
+
});
|
|
3750
|
+
await triggerWorkflowDelete(
|
|
3751
|
+
workflowContext,
|
|
3752
|
+
result2,
|
|
3753
|
+
false,
|
|
3754
|
+
middlewareManager.dispatchDebug.bind(middlewareManager)
|
|
3755
|
+
);
|
|
3756
|
+
},
|
|
3757
|
+
onCancel: async () => {
|
|
3758
|
+
await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
|
|
3759
|
+
},
|
|
3760
|
+
middlewareManager
|
|
3761
|
+
});
|
|
3762
|
+
if (result.isOk() && isInstanceOf(result.value, WorkflowNonRetryableError)) {
|
|
3763
|
+
return responseGenerator(
|
|
3764
|
+
createResponseData(workflowRunId, {
|
|
3765
|
+
condition: "non-retryable-error",
|
|
3766
|
+
result: result.value
|
|
3767
|
+
})
|
|
3768
|
+
);
|
|
3769
|
+
}
|
|
3770
|
+
if (result.isOk() && isInstanceOf(result.value, WorkflowRetryAfterError)) {
|
|
3771
|
+
return responseGenerator(
|
|
3772
|
+
createResponseData(workflowRunId, {
|
|
3773
|
+
condition: "retry-after-error",
|
|
3774
|
+
result: result.value
|
|
3775
|
+
})
|
|
3776
|
+
);
|
|
3777
|
+
}
|
|
3778
|
+
if (result.isErr()) {
|
|
3779
|
+
throw result.error;
|
|
3780
|
+
}
|
|
3781
|
+
await middlewareManager.dispatchDebug("onInfo", {
|
|
3782
|
+
info: `Workflow endpoint execution completed successfully.`
|
|
3783
|
+
});
|
|
3784
|
+
return responseGenerator(
|
|
3785
|
+
createResponseData(workflowContext.workflowRunId, {
|
|
3786
|
+
condition: "success"
|
|
3787
|
+
})
|
|
3788
|
+
);
|
|
3789
|
+
} else if (callReturnCheck.value === "workflow-ended") {
|
|
3790
|
+
return responseGenerator(
|
|
3791
|
+
createResponseData(workflowContext.workflowRunId, {
|
|
3792
|
+
condition: "workflow-already-ended"
|
|
3793
|
+
})
|
|
3794
|
+
);
|
|
3795
|
+
}
|
|
3796
|
+
return responseGenerator(
|
|
3797
|
+
createResponseData(workflowContext.workflowRunId, {
|
|
3798
|
+
condition: "fromCallback"
|
|
3799
|
+
})
|
|
3800
|
+
);
|
|
3801
|
+
};
|
|
3802
|
+
const safeHandler = async (request) => {
|
|
3803
|
+
const middlewareManager = new MiddlewareManager(middlewares);
|
|
3804
|
+
try {
|
|
3805
|
+
return await handler(request, middlewareManager);
|
|
3806
|
+
} catch (error) {
|
|
3807
|
+
const formattedError = formatWorkflowError(error);
|
|
3808
|
+
await middlewareManager.dispatchDebug("onError", {
|
|
3809
|
+
error: isInstanceOf(error, Error) ? error : new Error(formattedError.message)
|
|
3810
|
+
});
|
|
3811
|
+
return new Response(JSON.stringify(formattedError), {
|
|
3812
|
+
status: 500,
|
|
3813
|
+
headers: {
|
|
3814
|
+
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
|
|
3815
|
+
}
|
|
3816
|
+
});
|
|
3817
|
+
}
|
|
3818
|
+
};
|
|
3819
|
+
return { handler: safeHandler };
|
|
3820
|
+
};
|
|
3821
|
+
|
|
3822
|
+
// platforms/react-router.ts
|
|
3823
|
+
var telemetry = {
|
|
3824
|
+
sdk: SDK_TELEMETRY,
|
|
3825
|
+
framework: "react-router",
|
|
3826
|
+
runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
|
|
3827
|
+
};
|
|
3828
|
+
var serve = (routeFunction, options) => {
|
|
3829
|
+
const { handler: serveHandler } = serveBase(
|
|
3830
|
+
routeFunction,
|
|
3831
|
+
telemetry,
|
|
3832
|
+
options
|
|
3833
|
+
);
|
|
3834
|
+
return async ({ request }) => {
|
|
3835
|
+
return await serveHandler(request);
|
|
3836
|
+
};
|
|
3837
|
+
};
|
|
3838
|
+
var createWorkflow = (...params) => {
|
|
3839
|
+
const [routeFunction, options = {}] = params;
|
|
3840
|
+
return {
|
|
3841
|
+
routeFunction,
|
|
3842
|
+
options,
|
|
3843
|
+
workflowId: void 0
|
|
3844
|
+
};
|
|
3845
|
+
};
|
|
3846
|
+
var serveMany = (workflows, options) => {
|
|
3847
|
+
return serveManyBase({
|
|
3848
|
+
workflows,
|
|
3849
|
+
getUrl(params) {
|
|
3850
|
+
return params.request.url;
|
|
3851
|
+
},
|
|
3852
|
+
serveMethod: (...params) => serve(...params),
|
|
3853
|
+
options
|
|
3854
|
+
}).handler;
|
|
3855
|
+
};
|
|
3856
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3857
|
+
0 && (module.exports = {
|
|
3858
|
+
createWorkflow,
|
|
3859
|
+
serve,
|
|
3860
|
+
serveMany
|
|
3861
|
+
});
|