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