next-workflow-builder 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -0
- package/dist/chunk-3MSAF2TH.js +438 -0
- package/dist/chunk-5YYA34YV.js +96 -0
- package/dist/chunk-7MUXUHEL.js +66 -0
- package/dist/chunk-BNYDOC3I.js +169 -0
- package/dist/chunk-D44JFQYX.js +546 -0
- package/dist/chunk-DJ7ANVJ3.js +51 -0
- package/dist/chunk-O3I2INCD.js +71 -0
- package/dist/chunk-OQHML4II.js +36 -0
- package/dist/chunk-P3DTV3QS.js +105 -0
- package/dist/chunk-XJ67EFQA.js +1162 -0
- package/dist/chunk-Z3BJJYHM.js +246 -0
- package/dist/client/index.d.ts +32 -0
- package/dist/client/index.js +13700 -0
- package/dist/condition-SFT7Y5YJ.js +29 -0
- package/dist/database-query-GRWP3S3M.js +99 -0
- package/dist/http-request-2HVCXQHK.js +76 -0
- package/dist/next/index.d.ts +42 -0
- package/dist/next/index.js +66 -0
- package/dist/plugins/index.d.ts +113 -0
- package/dist/plugins/index.js +52 -0
- package/dist/server/api/index.d.ts +8 -0
- package/dist/server/api/index.js +2672 -0
- package/dist/server/index.d.ts +2911 -0
- package/dist/server/index.js +60 -0
- package/dist/style-prefixed.css +5167 -0
- package/dist/styles.css +5167 -0
- package/dist/types-BACZx2Ft.d.ts +139 -0
- package/package.json +112 -0
- package/src/scripts/nwb.ts +54 -0
|
@@ -0,0 +1,2672 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ARRAY_INDEX_PATTERN,
|
|
3
|
+
analyzeNodeUsage,
|
|
4
|
+
buildEdgeMap,
|
|
5
|
+
conditionAction,
|
|
6
|
+
databaseQueryAction,
|
|
7
|
+
escapeForTemplateLiteral,
|
|
8
|
+
findTriggerNodes,
|
|
9
|
+
generateWorkflowModule,
|
|
10
|
+
httpRequestAction,
|
|
11
|
+
sanitizeFunctionName,
|
|
12
|
+
sanitizeStepName,
|
|
13
|
+
sanitizeVarName
|
|
14
|
+
} from "../../chunk-XJ67EFQA.js";
|
|
15
|
+
import {
|
|
16
|
+
auth,
|
|
17
|
+
getAuthConfig
|
|
18
|
+
} from "../../chunk-P3DTV3QS.js";
|
|
19
|
+
import {
|
|
20
|
+
getErrorMessageAsync
|
|
21
|
+
} from "../../chunk-5YYA34YV.js";
|
|
22
|
+
import {
|
|
23
|
+
BOILERPLATE_PATH,
|
|
24
|
+
CODEGEN_TEMPLATES_PATH,
|
|
25
|
+
NON_ALPHANUMERIC_REGEX,
|
|
26
|
+
TEMPLATE_EXPORT_REGEX,
|
|
27
|
+
WHITESPACE_SPLIT_REGEX
|
|
28
|
+
} from "../../chunk-OQHML4II.js";
|
|
29
|
+
import {
|
|
30
|
+
createIntegration,
|
|
31
|
+
deleteIntegration,
|
|
32
|
+
getIntegration,
|
|
33
|
+
getIntegrations,
|
|
34
|
+
updateIntegration,
|
|
35
|
+
validateWorkflowIntegrations
|
|
36
|
+
} from "../../chunk-BNYDOC3I.js";
|
|
37
|
+
import {
|
|
38
|
+
findActionById,
|
|
39
|
+
getAllEnvVars,
|
|
40
|
+
getDependenciesForActions
|
|
41
|
+
} from "../../chunk-Z3BJJYHM.js";
|
|
42
|
+
import {
|
|
43
|
+
accounts,
|
|
44
|
+
apiKeys,
|
|
45
|
+
db,
|
|
46
|
+
generateId,
|
|
47
|
+
logWorkflowComplete,
|
|
48
|
+
users,
|
|
49
|
+
withStepLogging,
|
|
50
|
+
workflowExecutionLogs,
|
|
51
|
+
workflowExecutions,
|
|
52
|
+
workflows
|
|
53
|
+
} from "../../chunk-3MSAF2TH.js";
|
|
54
|
+
|
|
55
|
+
// src/server/api/index.ts
|
|
56
|
+
import { NextResponse as NextResponse5 } from "next/server";
|
|
57
|
+
|
|
58
|
+
// src/server/api/api-keys.ts
|
|
59
|
+
import { and, eq } from "drizzle-orm";
|
|
60
|
+
import { NextResponse } from "next/server";
|
|
61
|
+
import { createHash } from "crypto";
|
|
62
|
+
|
|
63
|
+
// src/server/auth/resolve-user.ts
|
|
64
|
+
async function resolveUser(request) {
|
|
65
|
+
const config = getAuthConfig();
|
|
66
|
+
if (config.getUser) {
|
|
67
|
+
return config.getUser(request);
|
|
68
|
+
}
|
|
69
|
+
const session = await auth.api.getSession({
|
|
70
|
+
headers: request.headers
|
|
71
|
+
});
|
|
72
|
+
if (!session?.user) return null;
|
|
73
|
+
return {
|
|
74
|
+
id: session.user.id,
|
|
75
|
+
email: session.user.email ?? void 0,
|
|
76
|
+
name: session.user.name ?? void 0
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/server/api/api-keys.ts
|
|
81
|
+
async function handleGetApiKeys(request) {
|
|
82
|
+
try {
|
|
83
|
+
const user = await resolveUser(request);
|
|
84
|
+
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
85
|
+
const keys = await db.query.apiKeys.findMany({
|
|
86
|
+
where: eq(apiKeys.userId, user.id),
|
|
87
|
+
columns: {
|
|
88
|
+
id: true,
|
|
89
|
+
name: true,
|
|
90
|
+
keyPrefix: true,
|
|
91
|
+
createdAt: true,
|
|
92
|
+
lastUsedAt: true
|
|
93
|
+
},
|
|
94
|
+
orderBy: (table, { desc: descFn }) => [descFn(table.createdAt)]
|
|
95
|
+
});
|
|
96
|
+
return NextResponse.json(keys);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("Failed to list API keys:", error);
|
|
99
|
+
return NextResponse.json({ error: "Failed to list API keys" }, { status: 500 });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function handleCreateApiKey(request) {
|
|
103
|
+
try {
|
|
104
|
+
const user = await resolveUser(request);
|
|
105
|
+
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
106
|
+
const isAnonymous = user.name === "Anonymous" || user.email?.startsWith("temp-");
|
|
107
|
+
if (isAnonymous) {
|
|
108
|
+
return NextResponse.json(
|
|
109
|
+
{ error: "Anonymous users cannot create API keys" },
|
|
110
|
+
{ status: 403 }
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const body = await request.json().catch(() => ({}));
|
|
114
|
+
const name = body.name || null;
|
|
115
|
+
const { randomBytes } = await import("crypto");
|
|
116
|
+
const randomPart = randomBytes(24).toString("base64url");
|
|
117
|
+
const key = `wfb_${randomPart}`;
|
|
118
|
+
const hash = createHash("sha256").update(key).digest("hex");
|
|
119
|
+
const prefix = key.slice(0, 11);
|
|
120
|
+
const [newKey] = await db.insert(apiKeys).values({
|
|
121
|
+
userId: user.id,
|
|
122
|
+
name,
|
|
123
|
+
keyHash: hash,
|
|
124
|
+
keyPrefix: prefix
|
|
125
|
+
}).returning({
|
|
126
|
+
id: apiKeys.id,
|
|
127
|
+
name: apiKeys.name,
|
|
128
|
+
keyPrefix: apiKeys.keyPrefix,
|
|
129
|
+
createdAt: apiKeys.createdAt
|
|
130
|
+
});
|
|
131
|
+
return NextResponse.json({ ...newKey, key });
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("Failed to create API key:", error);
|
|
134
|
+
return NextResponse.json({ error: "Failed to create API key" }, { status: 500 });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function handleDeleteApiKey(request, keyId) {
|
|
138
|
+
try {
|
|
139
|
+
const user = await resolveUser(request);
|
|
140
|
+
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
141
|
+
const result = await db.delete(apiKeys).where(and(eq(apiKeys.id, keyId), eq(apiKeys.userId, user.id))).returning({ id: apiKeys.id });
|
|
142
|
+
if (result.length === 0) {
|
|
143
|
+
return NextResponse.json({ error: "API key not found" }, { status: 404 });
|
|
144
|
+
}
|
|
145
|
+
return NextResponse.json({ success: true });
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error("Failed to delete API key:", error);
|
|
148
|
+
return NextResponse.json({ error: "Failed to delete API key" }, { status: 500 });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/server/api/auth.ts
|
|
153
|
+
import { toNextJsHandler } from "better-auth/next-js";
|
|
154
|
+
|
|
155
|
+
// src/server/api/utils.ts
|
|
156
|
+
import { eq as eq2 } from "drizzle-orm";
|
|
157
|
+
import { readdir, readFile } from "fs/promises";
|
|
158
|
+
import { join } from "path";
|
|
159
|
+
|
|
160
|
+
// src/server/lib/condition-validator.ts
|
|
161
|
+
var DANGEROUS_PATTERNS = [
|
|
162
|
+
// Assignment operators
|
|
163
|
+
/(?<![=!<>])=(?!=)/g,
|
|
164
|
+
// = but not ==, ===, !=, !==, <=, >=
|
|
165
|
+
/\+=|-=|\*=|\/=|%=|\^=|\|=|&=/g,
|
|
166
|
+
// Code execution
|
|
167
|
+
/\beval\s*\(/gi,
|
|
168
|
+
/\bFunction\s*\(/gi,
|
|
169
|
+
/\bimport\s*\(/gi,
|
|
170
|
+
/\brequire\s*\(/gi,
|
|
171
|
+
/\bnew\s+\w/gi,
|
|
172
|
+
// Dangerous globals
|
|
173
|
+
/\bprocess\b/gi,
|
|
174
|
+
/\bglobal\b/gi,
|
|
175
|
+
/\bwindow\b/gi,
|
|
176
|
+
/\bdocument\b/gi,
|
|
177
|
+
/\bconstructor\b/gi,
|
|
178
|
+
/\b__proto__\b/gi,
|
|
179
|
+
/\bprototype\b/gi,
|
|
180
|
+
// Control flow that could be exploited
|
|
181
|
+
/\bwhile\s*\(/gi,
|
|
182
|
+
/\bfor\s*\(/gi,
|
|
183
|
+
/\bdo\s*\{/gi,
|
|
184
|
+
/\bswitch\s*\(/gi,
|
|
185
|
+
/\btry\s*\{/gi,
|
|
186
|
+
/\bcatch\s*\(/gi,
|
|
187
|
+
/\bfinally\s*\{/gi,
|
|
188
|
+
/\bthrow\s+/gi,
|
|
189
|
+
/\breturn\s+/gi,
|
|
190
|
+
// Template literals with expressions (could execute code)
|
|
191
|
+
/`[^`]*\$\{/g,
|
|
192
|
+
// Object literals (but NOT bracket property access)
|
|
193
|
+
/\{\s*\w+\s*:/g,
|
|
194
|
+
// Increment/decrement
|
|
195
|
+
/\+\+|--/g,
|
|
196
|
+
// Bitwise operators (rarely needed, often used in exploits)
|
|
197
|
+
/<<|>>|>>>/g,
|
|
198
|
+
// Comma operator (can chain expressions)
|
|
199
|
+
/,(?![^(]*\))/g,
|
|
200
|
+
// Comma not inside function call parentheses
|
|
201
|
+
// Semicolons (statement separator)
|
|
202
|
+
/;/g
|
|
203
|
+
];
|
|
204
|
+
var ALLOWED_METHODS = /* @__PURE__ */ new Set([
|
|
205
|
+
"includes",
|
|
206
|
+
"startsWith",
|
|
207
|
+
"endsWith",
|
|
208
|
+
"toString",
|
|
209
|
+
"toLowerCase",
|
|
210
|
+
"toUpperCase",
|
|
211
|
+
"trim",
|
|
212
|
+
"length"
|
|
213
|
+
// Actually a property, but accessed like .length
|
|
214
|
+
]);
|
|
215
|
+
var METHOD_CALL_PATTERN = /\.(\w+)\s*\(/g;
|
|
216
|
+
var BRACKET_EXPRESSION_PATTERN = /(\w+)\s*\[([^\]]+)\]/g;
|
|
217
|
+
var VALID_BRACKET_ACCESS_PATTERN = /^__v\d+$/;
|
|
218
|
+
var VALID_BRACKET_CONTENT_PATTERN = /^(\d+|'[^']*'|"[^"]*")$/;
|
|
219
|
+
var WHITESPACE_SPLIT_PATTERN = /\s+/;
|
|
220
|
+
var VARIABLE_TOKEN_PATTERN = /^__v\d+/;
|
|
221
|
+
var STRING_TOKEN_PATTERN = /^['"]/;
|
|
222
|
+
var NUMBER_TOKEN_PATTERN = /^\d/;
|
|
223
|
+
var LITERAL_TOKEN_PATTERN = /^(true|false|null|undefined)$/;
|
|
224
|
+
var OPERATOR_TOKEN_PATTERN = /^(===|!==|==|!=|>=|<=|>|<|&&|\|\||!|\(|\))$/;
|
|
225
|
+
var IDENTIFIER_TOKEN_PATTERN = /^[a-zA-Z_]\w*$/;
|
|
226
|
+
function checkDangerousPatterns(expression) {
|
|
227
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
228
|
+
pattern.lastIndex = 0;
|
|
229
|
+
if (pattern.test(expression)) {
|
|
230
|
+
pattern.lastIndex = 0;
|
|
231
|
+
const match = expression.match(pattern);
|
|
232
|
+
return {
|
|
233
|
+
valid: false,
|
|
234
|
+
error: `Condition contains disallowed syntax: "${match?.[0] || "unknown"}"`
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return { valid: true };
|
|
239
|
+
}
|
|
240
|
+
function checkBracketExpressions(expression) {
|
|
241
|
+
BRACKET_EXPRESSION_PATTERN.lastIndex = 0;
|
|
242
|
+
let match = null;
|
|
243
|
+
while (true) {
|
|
244
|
+
match = BRACKET_EXPRESSION_PATTERN.exec(expression);
|
|
245
|
+
if (match === null) {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
const beforeBracket = match[1];
|
|
249
|
+
const insideBracket = match[2].trim();
|
|
250
|
+
if (!VALID_BRACKET_ACCESS_PATTERN.test(beforeBracket)) {
|
|
251
|
+
return {
|
|
252
|
+
valid: false,
|
|
253
|
+
error: `Bracket notation is only allowed on workflow variables. Found: "${beforeBracket}[...]"`
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (!VALID_BRACKET_CONTENT_PATTERN.test(insideBracket)) {
|
|
257
|
+
return {
|
|
258
|
+
valid: false,
|
|
259
|
+
error: `Invalid bracket content: "[${insideBracket}]". Only numeric indices or string literals are allowed.`
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const standaloneArrayPattern = /(?:^|[=!<>&|(\s])\s*\[/g;
|
|
264
|
+
standaloneArrayPattern.lastIndex = 0;
|
|
265
|
+
if (standaloneArrayPattern.test(expression)) {
|
|
266
|
+
return {
|
|
267
|
+
valid: false,
|
|
268
|
+
error: "Array literals are not allowed in conditions. Use workflow variables instead."
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return { valid: true };
|
|
272
|
+
}
|
|
273
|
+
function checkMethodCalls(expression) {
|
|
274
|
+
METHOD_CALL_PATTERN.lastIndex = 0;
|
|
275
|
+
let match = null;
|
|
276
|
+
while (true) {
|
|
277
|
+
match = METHOD_CALL_PATTERN.exec(expression);
|
|
278
|
+
if (match === null) {
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
const methodName = match[1];
|
|
282
|
+
if (!ALLOWED_METHODS.has(methodName)) {
|
|
283
|
+
return {
|
|
284
|
+
valid: false,
|
|
285
|
+
error: `Method "${methodName}" is not allowed in conditions. Allowed methods: ${Array.from(ALLOWED_METHODS).join(", ")}`
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { valid: true };
|
|
290
|
+
}
|
|
291
|
+
function checkParentheses(expression) {
|
|
292
|
+
let parenDepth = 0;
|
|
293
|
+
for (const char of expression) {
|
|
294
|
+
if (char === "(") {
|
|
295
|
+
parenDepth += 1;
|
|
296
|
+
}
|
|
297
|
+
if (char === ")") {
|
|
298
|
+
parenDepth -= 1;
|
|
299
|
+
}
|
|
300
|
+
if (parenDepth < 0) {
|
|
301
|
+
return { valid: false, error: "Unbalanced parentheses in condition" };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (parenDepth !== 0) {
|
|
305
|
+
return { valid: false, error: "Unbalanced parentheses in condition" };
|
|
306
|
+
}
|
|
307
|
+
return { valid: true };
|
|
308
|
+
}
|
|
309
|
+
function isValidToken(token) {
|
|
310
|
+
if (VARIABLE_TOKEN_PATTERN.test(token)) {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
if (STRING_TOKEN_PATTERN.test(token)) {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
if (NUMBER_TOKEN_PATTERN.test(token)) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
if (LITERAL_TOKEN_PATTERN.test(token)) {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
if (OPERATOR_TOKEN_PATTERN.test(token)) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
function checkUnauthorizedIdentifiers(expression) {
|
|
328
|
+
const tokens = expression.split(WHITESPACE_SPLIT_PATTERN).filter(Boolean);
|
|
329
|
+
for (const token of tokens) {
|
|
330
|
+
if (isValidToken(token)) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (IDENTIFIER_TOKEN_PATTERN.test(token) && !token.startsWith("__v")) {
|
|
334
|
+
return {
|
|
335
|
+
valid: false,
|
|
336
|
+
error: `Unknown identifier "${token}" in condition. Use template variables like {{@nodeId:Label.field}} to reference workflow data.`
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return { valid: true };
|
|
341
|
+
}
|
|
342
|
+
function validateConditionExpression(expression) {
|
|
343
|
+
if (!expression || expression.trim() === "") {
|
|
344
|
+
return { valid: false, error: "Condition expression cannot be empty" };
|
|
345
|
+
}
|
|
346
|
+
const dangerousCheck = checkDangerousPatterns(expression);
|
|
347
|
+
if (!dangerousCheck.valid) {
|
|
348
|
+
return dangerousCheck;
|
|
349
|
+
}
|
|
350
|
+
const bracketCheck = checkBracketExpressions(expression);
|
|
351
|
+
if (!bracketCheck.valid) {
|
|
352
|
+
return bracketCheck;
|
|
353
|
+
}
|
|
354
|
+
const methodCheck = checkMethodCalls(expression);
|
|
355
|
+
if (!methodCheck.valid) {
|
|
356
|
+
return methodCheck;
|
|
357
|
+
}
|
|
358
|
+
const parenCheck = checkParentheses(expression);
|
|
359
|
+
if (!parenCheck.valid) {
|
|
360
|
+
return parenCheck;
|
|
361
|
+
}
|
|
362
|
+
const identifierCheck = checkUnauthorizedIdentifiers(expression);
|
|
363
|
+
if (!identifierCheck.valid) {
|
|
364
|
+
return identifierCheck;
|
|
365
|
+
}
|
|
366
|
+
return { valid: true };
|
|
367
|
+
}
|
|
368
|
+
function preValidateConditionExpression(expression) {
|
|
369
|
+
if (!expression || typeof expression !== "string") {
|
|
370
|
+
return { valid: false, error: "Condition must be a non-empty string" };
|
|
371
|
+
}
|
|
372
|
+
const dangerousKeywords = [
|
|
373
|
+
"eval",
|
|
374
|
+
"Function",
|
|
375
|
+
"import",
|
|
376
|
+
"require",
|
|
377
|
+
"process",
|
|
378
|
+
"global",
|
|
379
|
+
"window",
|
|
380
|
+
"document",
|
|
381
|
+
"__proto__",
|
|
382
|
+
"constructor",
|
|
383
|
+
"prototype"
|
|
384
|
+
];
|
|
385
|
+
const lowerExpression = expression.toLowerCase();
|
|
386
|
+
for (const keyword of dangerousKeywords) {
|
|
387
|
+
if (lowerExpression.includes(keyword.toLowerCase())) {
|
|
388
|
+
return {
|
|
389
|
+
valid: false,
|
|
390
|
+
error: `Condition contains disallowed keyword: "${keyword}"`
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return { valid: true };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/server/lib/steps/trigger.ts
|
|
398
|
+
import "server-only";
|
|
399
|
+
function executeTrigger(input) {
|
|
400
|
+
return {
|
|
401
|
+
success: true,
|
|
402
|
+
data: input.triggerData
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
async function triggerStep(input) {
|
|
406
|
+
"use step";
|
|
407
|
+
if (input._workflowComplete) {
|
|
408
|
+
await logWorkflowComplete(input._workflowComplete);
|
|
409
|
+
return { success: true, data: {} };
|
|
410
|
+
}
|
|
411
|
+
return withStepLogging(input, () => Promise.resolve(executeTrigger(input)));
|
|
412
|
+
}
|
|
413
|
+
triggerStep.maxRetries = 0;
|
|
414
|
+
|
|
415
|
+
// src/server/lib/workflow-executor.workflow.ts
|
|
416
|
+
var SYSTEM_ACTIONS = {
|
|
417
|
+
"Database Query": {
|
|
418
|
+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic module import
|
|
419
|
+
importer: () => import("../../database-query-GRWP3S3M.js"),
|
|
420
|
+
stepFunction: "databaseQueryStep"
|
|
421
|
+
},
|
|
422
|
+
"HTTP Request": {
|
|
423
|
+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic module import
|
|
424
|
+
importer: () => import("../../http-request-2HVCXQHK.js"),
|
|
425
|
+
stepFunction: "httpRequestStep"
|
|
426
|
+
},
|
|
427
|
+
Condition: {
|
|
428
|
+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic module import
|
|
429
|
+
importer: () => import("../../condition-SFT7Y5YJ.js"),
|
|
430
|
+
stepFunction: "conditionStep"
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
function replaceTemplateVariable(match, nodeId, rest, outputs, evalContext, varCounter) {
|
|
434
|
+
const sanitizedNodeId = nodeId.replace(/[^a-zA-Z0-9]/g, "_");
|
|
435
|
+
const output = outputs[sanitizedNodeId];
|
|
436
|
+
if (!output) {
|
|
437
|
+
console.log("[Condition] Output not found for node:", sanitizedNodeId);
|
|
438
|
+
return match;
|
|
439
|
+
}
|
|
440
|
+
const dotIndex = rest.indexOf(".");
|
|
441
|
+
let value;
|
|
442
|
+
if (dotIndex === -1) {
|
|
443
|
+
value = output.data;
|
|
444
|
+
} else if (output.data === null || output.data === void 0) {
|
|
445
|
+
value = void 0;
|
|
446
|
+
} else {
|
|
447
|
+
const fieldPath = rest.substring(dotIndex + 1);
|
|
448
|
+
const fields = fieldPath.split(".");
|
|
449
|
+
let current = output.data;
|
|
450
|
+
const firstField = fields[0];
|
|
451
|
+
if (current && typeof current === "object" && "success" in current && "data" in current && firstField !== "success" && firstField !== "data" && firstField !== "error") {
|
|
452
|
+
current = current.data;
|
|
453
|
+
}
|
|
454
|
+
for (const field of fields) {
|
|
455
|
+
if (current && typeof current === "object") {
|
|
456
|
+
current = current[field];
|
|
457
|
+
} else {
|
|
458
|
+
console.log("[Condition] Field access failed:", fieldPath);
|
|
459
|
+
value = void 0;
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (value === void 0 && current !== void 0) {
|
|
464
|
+
value = current;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const varName = `__v${varCounter.value}`;
|
|
468
|
+
varCounter.value += 1;
|
|
469
|
+
evalContext[varName] = value;
|
|
470
|
+
return varName;
|
|
471
|
+
}
|
|
472
|
+
function evaluateConditionExpression(conditionExpression, outputs) {
|
|
473
|
+
console.log("[Condition] Original expression:", conditionExpression);
|
|
474
|
+
if (typeof conditionExpression === "boolean") {
|
|
475
|
+
return { result: conditionExpression, resolvedValues: {} };
|
|
476
|
+
}
|
|
477
|
+
if (typeof conditionExpression === "string") {
|
|
478
|
+
const preValidation = preValidateConditionExpression(conditionExpression);
|
|
479
|
+
if (!preValidation.valid) {
|
|
480
|
+
console.error("[Condition] Pre-validation failed:", preValidation.error);
|
|
481
|
+
console.error("[Condition] Expression was:", conditionExpression);
|
|
482
|
+
return { result: false, resolvedValues: {} };
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
const evalContext = {};
|
|
486
|
+
const resolvedValues = {};
|
|
487
|
+
let transformedExpression = conditionExpression;
|
|
488
|
+
const templatePattern = /\{\{@([^:]+):([^}]+)\}\}/g;
|
|
489
|
+
const varCounter = { value: 0 };
|
|
490
|
+
transformedExpression = transformedExpression.replace(
|
|
491
|
+
templatePattern,
|
|
492
|
+
(match, nodeId, rest) => {
|
|
493
|
+
const varName = replaceTemplateVariable(
|
|
494
|
+
match,
|
|
495
|
+
nodeId,
|
|
496
|
+
rest,
|
|
497
|
+
outputs,
|
|
498
|
+
evalContext,
|
|
499
|
+
varCounter
|
|
500
|
+
);
|
|
501
|
+
resolvedValues[rest] = evalContext[varName];
|
|
502
|
+
return varName;
|
|
503
|
+
}
|
|
504
|
+
);
|
|
505
|
+
const validation = validateConditionExpression(transformedExpression);
|
|
506
|
+
if (!validation.valid) {
|
|
507
|
+
console.error("[Condition] Validation failed:", validation.error);
|
|
508
|
+
console.error("[Condition] Original expression:", conditionExpression);
|
|
509
|
+
console.error(
|
|
510
|
+
"[Condition] Transformed expression:",
|
|
511
|
+
transformedExpression
|
|
512
|
+
);
|
|
513
|
+
return { result: false, resolvedValues };
|
|
514
|
+
}
|
|
515
|
+
const varNames = Object.keys(evalContext);
|
|
516
|
+
const varValues = Object.values(evalContext);
|
|
517
|
+
const evalFunc = new Function(
|
|
518
|
+
...varNames,
|
|
519
|
+
`return (${transformedExpression});`
|
|
520
|
+
);
|
|
521
|
+
const result = evalFunc(...varValues);
|
|
522
|
+
return { result: Boolean(result), resolvedValues };
|
|
523
|
+
} catch (error) {
|
|
524
|
+
console.error("[Condition] Failed to evaluate condition:", error);
|
|
525
|
+
console.error("[Condition] Expression was:", conditionExpression);
|
|
526
|
+
return { result: false, resolvedValues: {} };
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return { result: Boolean(conditionExpression), resolvedValues: {} };
|
|
530
|
+
}
|
|
531
|
+
async function executeActionStep(input) {
|
|
532
|
+
const { actionType, config, outputs, context } = input;
|
|
533
|
+
const stepInput = {
|
|
534
|
+
...config,
|
|
535
|
+
_context: context
|
|
536
|
+
};
|
|
537
|
+
if (actionType === "Condition") {
|
|
538
|
+
const systemAction2 = SYSTEM_ACTIONS.Condition;
|
|
539
|
+
const module = await systemAction2.importer();
|
|
540
|
+
const originalExpression = stepInput.condition;
|
|
541
|
+
const { result: evaluatedCondition, resolvedValues } = evaluateConditionExpression(originalExpression, outputs);
|
|
542
|
+
console.log("[Condition] Final result:", evaluatedCondition);
|
|
543
|
+
return await module[systemAction2.stepFunction]({
|
|
544
|
+
condition: evaluatedCondition,
|
|
545
|
+
// Include original expression and resolved values for logging purposes
|
|
546
|
+
expression: typeof originalExpression === "string" ? originalExpression : void 0,
|
|
547
|
+
values: Object.keys(resolvedValues).length > 0 ? resolvedValues : void 0,
|
|
548
|
+
_context: context
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const systemAction = SYSTEM_ACTIONS[actionType];
|
|
552
|
+
if (systemAction) {
|
|
553
|
+
const module = await systemAction.importer();
|
|
554
|
+
const stepFunction = module[systemAction.stepFunction];
|
|
555
|
+
return await stepFunction(stepInput);
|
|
556
|
+
}
|
|
557
|
+
const { getStepImporter } = await import("virtual:workflow-builder-step-registry");
|
|
558
|
+
const stepImporter = getStepImporter(actionType);
|
|
559
|
+
if (stepImporter) {
|
|
560
|
+
const module = await stepImporter.importer();
|
|
561
|
+
const stepFunction = module[stepImporter.stepFunction];
|
|
562
|
+
if (stepFunction) {
|
|
563
|
+
return await stepFunction(stepInput);
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
success: false,
|
|
567
|
+
error: `Step function "${stepImporter.stepFunction}" not found in module for action "${actionType}". Check that the plugin exports the correct function name.`
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
success: false,
|
|
572
|
+
error: `Unknown action type: "${actionType}". This action is not registered in the plugin system. Available system actions: ${Object.keys(SYSTEM_ACTIONS).join(", ")}.`
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function processTemplates(config, outputs) {
|
|
576
|
+
const processed = {};
|
|
577
|
+
for (const [key, value] of Object.entries(config)) {
|
|
578
|
+
if (typeof value === "string") {
|
|
579
|
+
let processedValue = value;
|
|
580
|
+
const templatePattern = /\{\{@([^:]+):([^}]+)\}\}/g;
|
|
581
|
+
processedValue = processedValue.replace(
|
|
582
|
+
templatePattern,
|
|
583
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Template processing requires nested logic
|
|
584
|
+
(match, nodeId, rest) => {
|
|
585
|
+
const sanitizedNodeId = nodeId.replace(/[^a-zA-Z0-9]/g, "_");
|
|
586
|
+
const output = outputs[sanitizedNodeId];
|
|
587
|
+
if (!output) {
|
|
588
|
+
return match;
|
|
589
|
+
}
|
|
590
|
+
const dotIndex = rest.indexOf(".");
|
|
591
|
+
if (dotIndex === -1) {
|
|
592
|
+
const data = output.data;
|
|
593
|
+
if (data === null || data === void 0) {
|
|
594
|
+
return "";
|
|
595
|
+
}
|
|
596
|
+
if (typeof data === "object") {
|
|
597
|
+
return JSON.stringify(data);
|
|
598
|
+
}
|
|
599
|
+
return String(data);
|
|
600
|
+
}
|
|
601
|
+
if (output.data === null || output.data === void 0) {
|
|
602
|
+
return "";
|
|
603
|
+
}
|
|
604
|
+
const fieldPath = rest.substring(dotIndex + 1);
|
|
605
|
+
const fields = fieldPath.split(".");
|
|
606
|
+
let current = output.data;
|
|
607
|
+
const firstField = fields[0];
|
|
608
|
+
if (current && typeof current === "object" && "success" in current && "data" in current && firstField !== "success" && firstField !== "data" && firstField !== "error") {
|
|
609
|
+
current = current.data;
|
|
610
|
+
}
|
|
611
|
+
for (const field of fields) {
|
|
612
|
+
if (current && typeof current === "object") {
|
|
613
|
+
current = current[field];
|
|
614
|
+
} else {
|
|
615
|
+
return "";
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (current === null || current === void 0) {
|
|
619
|
+
return "";
|
|
620
|
+
}
|
|
621
|
+
if (typeof current === "object") {
|
|
622
|
+
return JSON.stringify(current);
|
|
623
|
+
}
|
|
624
|
+
return String(current);
|
|
625
|
+
}
|
|
626
|
+
);
|
|
627
|
+
processed[key] = processedValue;
|
|
628
|
+
} else {
|
|
629
|
+
processed[key] = value;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return processed;
|
|
633
|
+
}
|
|
634
|
+
async function executeWorkflow(input) {
|
|
635
|
+
"use workflow";
|
|
636
|
+
console.log("[Workflow Executor] Starting workflow execution");
|
|
637
|
+
const { nodes, edges, triggerInput = {}, executionId, workflowId } = input;
|
|
638
|
+
console.log("[Workflow Executor] Input:", {
|
|
639
|
+
nodeCount: nodes.length,
|
|
640
|
+
edgeCount: edges.length,
|
|
641
|
+
hasExecutionId: !!executionId,
|
|
642
|
+
workflowId: workflowId || "none"
|
|
643
|
+
});
|
|
644
|
+
const outputs = {};
|
|
645
|
+
const results = {};
|
|
646
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
647
|
+
const edgesBySource = /* @__PURE__ */ new Map();
|
|
648
|
+
for (const edge of edges) {
|
|
649
|
+
const targets = edgesBySource.get(edge.source) || [];
|
|
650
|
+
targets.push(edge.target);
|
|
651
|
+
edgesBySource.set(edge.source, targets);
|
|
652
|
+
}
|
|
653
|
+
const nodesWithIncoming = new Set(edges.map((e) => e.target));
|
|
654
|
+
const triggerNodes = nodes.filter(
|
|
655
|
+
(node) => node.data.type === "trigger" && !nodesWithIncoming.has(node.id)
|
|
656
|
+
);
|
|
657
|
+
console.log(
|
|
658
|
+
"[Workflow Executor] Found",
|
|
659
|
+
triggerNodes.length,
|
|
660
|
+
"trigger nodes"
|
|
661
|
+
);
|
|
662
|
+
const { getActionLabel } = await import("virtual:workflow-builder-step-registry");
|
|
663
|
+
function getNodeName(node) {
|
|
664
|
+
if (node.data.label) {
|
|
665
|
+
return node.data.label;
|
|
666
|
+
}
|
|
667
|
+
if (node.data.type === "action") {
|
|
668
|
+
const actionType = node.data.config?.actionType;
|
|
669
|
+
if (actionType) {
|
|
670
|
+
const label = getActionLabel(actionType);
|
|
671
|
+
if (label) {
|
|
672
|
+
return label;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return "Action";
|
|
676
|
+
}
|
|
677
|
+
if (node.data.type === "trigger") {
|
|
678
|
+
return node.data.config?.triggerType || "Trigger";
|
|
679
|
+
}
|
|
680
|
+
return node.data.type;
|
|
681
|
+
}
|
|
682
|
+
async function executeNode(nodeId, visited = /* @__PURE__ */ new Set()) {
|
|
683
|
+
console.log("[Workflow Executor] Executing node:", nodeId);
|
|
684
|
+
if (visited.has(nodeId)) {
|
|
685
|
+
console.log("[Workflow Executor] Node already visited, skipping");
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
visited.add(nodeId);
|
|
689
|
+
const node = nodeMap.get(nodeId);
|
|
690
|
+
if (!node) {
|
|
691
|
+
console.log("[Workflow Executor] Node not found:", nodeId);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
if (node.data.enabled === false) {
|
|
695
|
+
console.log("[Workflow Executor] Skipping disabled node:", nodeId);
|
|
696
|
+
const sanitizedNodeId = nodeId.replace(/[^a-zA-Z0-9]/g, "_");
|
|
697
|
+
outputs[sanitizedNodeId] = {
|
|
698
|
+
label: node.data.label || nodeId,
|
|
699
|
+
data: null
|
|
700
|
+
};
|
|
701
|
+
const nextNodes = edgesBySource.get(nodeId) || [];
|
|
702
|
+
await Promise.all(
|
|
703
|
+
nextNodes.map((nextNodeId) => executeNode(nextNodeId, visited))
|
|
704
|
+
);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
try {
|
|
708
|
+
let result;
|
|
709
|
+
if (node.data.type === "trigger") {
|
|
710
|
+
console.log("[Workflow Executor] Executing trigger node");
|
|
711
|
+
const config = node.data.config || {};
|
|
712
|
+
const triggerType = config.triggerType;
|
|
713
|
+
let triggerData = {
|
|
714
|
+
triggered: true,
|
|
715
|
+
timestamp: Date.now()
|
|
716
|
+
};
|
|
717
|
+
if (triggerType === "Webhook" && config.webhookMockRequest && (!triggerInput || Object.keys(triggerInput).length === 0)) {
|
|
718
|
+
try {
|
|
719
|
+
const mockData = JSON.parse(config.webhookMockRequest);
|
|
720
|
+
triggerData = { ...triggerData, ...mockData };
|
|
721
|
+
console.log(
|
|
722
|
+
"[Workflow Executor] Using webhook mock request data:",
|
|
723
|
+
mockData
|
|
724
|
+
);
|
|
725
|
+
} catch (error) {
|
|
726
|
+
console.error(
|
|
727
|
+
"[Workflow Executor] Failed to parse webhook mock request:",
|
|
728
|
+
error
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
} else if (triggerInput && Object.keys(triggerInput).length > 0) {
|
|
732
|
+
triggerData = { ...triggerData, ...triggerInput };
|
|
733
|
+
}
|
|
734
|
+
const triggerContext = {
|
|
735
|
+
executionId,
|
|
736
|
+
nodeId: node.id,
|
|
737
|
+
nodeName: getNodeName(node),
|
|
738
|
+
nodeType: node.data.type
|
|
739
|
+
};
|
|
740
|
+
const triggerResult = await triggerStep({
|
|
741
|
+
triggerData,
|
|
742
|
+
_context: triggerContext
|
|
743
|
+
});
|
|
744
|
+
result = {
|
|
745
|
+
success: triggerResult.success,
|
|
746
|
+
data: triggerResult.data
|
|
747
|
+
};
|
|
748
|
+
} else if (node.data.type === "action") {
|
|
749
|
+
const config = node.data.config || {};
|
|
750
|
+
const actionType = config.actionType;
|
|
751
|
+
console.log("[Workflow Executor] Executing action node:", actionType);
|
|
752
|
+
if (!actionType) {
|
|
753
|
+
result = {
|
|
754
|
+
success: false,
|
|
755
|
+
error: `Action node "${node.data.label || node.id}" has no action type configured`
|
|
756
|
+
};
|
|
757
|
+
results[nodeId] = result;
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const configWithoutCondition = { ...config };
|
|
761
|
+
const originalCondition = config.condition;
|
|
762
|
+
configWithoutCondition.condition = void 0;
|
|
763
|
+
const processedConfig = processTemplates(
|
|
764
|
+
configWithoutCondition,
|
|
765
|
+
outputs
|
|
766
|
+
);
|
|
767
|
+
if (originalCondition !== void 0) {
|
|
768
|
+
processedConfig.condition = originalCondition;
|
|
769
|
+
}
|
|
770
|
+
const stepContext = {
|
|
771
|
+
executionId,
|
|
772
|
+
nodeId: node.id,
|
|
773
|
+
nodeName: getNodeName(node),
|
|
774
|
+
nodeType: actionType
|
|
775
|
+
};
|
|
776
|
+
console.log("[Workflow Executor] Calling executeActionStep");
|
|
777
|
+
const stepResult = await executeActionStep({
|
|
778
|
+
actionType,
|
|
779
|
+
config: processedConfig,
|
|
780
|
+
outputs,
|
|
781
|
+
context: stepContext
|
|
782
|
+
});
|
|
783
|
+
console.log("[Workflow Executor] Step result received:", {
|
|
784
|
+
hasResult: !!stepResult,
|
|
785
|
+
resultType: typeof stepResult
|
|
786
|
+
});
|
|
787
|
+
const isErrorResult = stepResult && typeof stepResult === "object" && "success" in stepResult && stepResult.success === false;
|
|
788
|
+
if (isErrorResult) {
|
|
789
|
+
const errorResult = stepResult;
|
|
790
|
+
const errorMessage = typeof errorResult.error === "string" ? errorResult.error : errorResult.error?.message || `Step "${actionType}" in node "${node.data.label || node.id}" failed without a specific error message.`;
|
|
791
|
+
console.error(`[Workflow Executor] Step "${actionType}" failed:`, errorMessage);
|
|
792
|
+
result = {
|
|
793
|
+
success: false,
|
|
794
|
+
error: errorMessage
|
|
795
|
+
};
|
|
796
|
+
} else {
|
|
797
|
+
result = {
|
|
798
|
+
success: true,
|
|
799
|
+
data: stepResult
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
} else {
|
|
803
|
+
console.log("[Workflow Executor] Unknown node type:", node.data.type);
|
|
804
|
+
result = {
|
|
805
|
+
success: false,
|
|
806
|
+
error: `Unknown node type "${node.data.type}" in node "${node.data.label || node.id}". Expected "trigger" or "action".`
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
results[nodeId] = result;
|
|
810
|
+
const sanitizedNodeId = nodeId.replace(/[^a-zA-Z0-9]/g, "_");
|
|
811
|
+
outputs[sanitizedNodeId] = {
|
|
812
|
+
label: node.data.label || nodeId,
|
|
813
|
+
data: result.data
|
|
814
|
+
};
|
|
815
|
+
console.log("[Workflow Executor] Node execution completed:", {
|
|
816
|
+
nodeId,
|
|
817
|
+
success: result.success
|
|
818
|
+
});
|
|
819
|
+
if (result.success) {
|
|
820
|
+
const isConditionNode = node.data.type === "action" && node.data.config?.actionType === "Condition";
|
|
821
|
+
if (isConditionNode) {
|
|
822
|
+
const conditionResult = result.data?.condition;
|
|
823
|
+
console.log(
|
|
824
|
+
"[Workflow Executor] Condition node result:",
|
|
825
|
+
conditionResult
|
|
826
|
+
);
|
|
827
|
+
if (conditionResult === true) {
|
|
828
|
+
const nextNodes = edgesBySource.get(nodeId) || [];
|
|
829
|
+
console.log(
|
|
830
|
+
"[Workflow Executor] Condition is true, executing",
|
|
831
|
+
nextNodes.length,
|
|
832
|
+
"next nodes in parallel"
|
|
833
|
+
);
|
|
834
|
+
await Promise.all(
|
|
835
|
+
nextNodes.map((nextNodeId) => executeNode(nextNodeId, visited))
|
|
836
|
+
);
|
|
837
|
+
} else {
|
|
838
|
+
console.log(
|
|
839
|
+
"[Workflow Executor] Condition is false, skipping next nodes"
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
const nextNodes = edgesBySource.get(nodeId) || [];
|
|
844
|
+
console.log(
|
|
845
|
+
"[Workflow Executor] Executing",
|
|
846
|
+
nextNodes.length,
|
|
847
|
+
"next nodes in parallel"
|
|
848
|
+
);
|
|
849
|
+
await Promise.all(
|
|
850
|
+
nextNodes.map((nextNodeId) => executeNode(nextNodeId, visited))
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
} catch (error) {
|
|
855
|
+
console.error("[Workflow Executor] Error executing node:", nodeId, error);
|
|
856
|
+
const errorMessage = await getErrorMessageAsync(error);
|
|
857
|
+
const errorResult = {
|
|
858
|
+
success: false,
|
|
859
|
+
error: errorMessage
|
|
860
|
+
};
|
|
861
|
+
results[nodeId] = errorResult;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
try {
|
|
865
|
+
console.log("[Workflow Executor] Starting execution from trigger nodes");
|
|
866
|
+
const workflowStartTime = Date.now();
|
|
867
|
+
await Promise.all(triggerNodes.map((trigger) => executeNode(trigger.id)));
|
|
868
|
+
const finalSuccess = Object.values(results).every((r) => r.success);
|
|
869
|
+
const duration = Date.now() - workflowStartTime;
|
|
870
|
+
console.log("[Workflow Executor] Workflow execution completed:", {
|
|
871
|
+
success: finalSuccess,
|
|
872
|
+
resultCount: Object.keys(results).length,
|
|
873
|
+
duration
|
|
874
|
+
});
|
|
875
|
+
if (executionId) {
|
|
876
|
+
try {
|
|
877
|
+
await triggerStep({
|
|
878
|
+
triggerData: {},
|
|
879
|
+
_workflowComplete: {
|
|
880
|
+
executionId,
|
|
881
|
+
status: finalSuccess ? "success" : "error",
|
|
882
|
+
output: Object.values(results).at(-1)?.data,
|
|
883
|
+
error: Object.values(results).find((r) => !r.success)?.error,
|
|
884
|
+
startTime: workflowStartTime
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
console.log("[Workflow Executor] Updated execution record");
|
|
888
|
+
} catch (error) {
|
|
889
|
+
console.error(
|
|
890
|
+
"[Workflow Executor] Failed to update execution record:",
|
|
891
|
+
error
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
success: finalSuccess,
|
|
897
|
+
results,
|
|
898
|
+
outputs
|
|
899
|
+
};
|
|
900
|
+
} catch (error) {
|
|
901
|
+
console.error(
|
|
902
|
+
"[Workflow Executor] Fatal error during workflow execution:",
|
|
903
|
+
error
|
|
904
|
+
);
|
|
905
|
+
const errorMessage = await getErrorMessageAsync(error);
|
|
906
|
+
if (executionId) {
|
|
907
|
+
try {
|
|
908
|
+
await triggerStep({
|
|
909
|
+
triggerData: {},
|
|
910
|
+
_workflowComplete: {
|
|
911
|
+
executionId,
|
|
912
|
+
status: "error",
|
|
913
|
+
error: errorMessage,
|
|
914
|
+
startTime: Date.now()
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
} catch (logError) {
|
|
918
|
+
console.error("[Workflow Executor] Failed to log error:", logError);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return {
|
|
922
|
+
success: false,
|
|
923
|
+
results,
|
|
924
|
+
outputs,
|
|
925
|
+
error: errorMessage
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// src/server/api/utils.ts
|
|
931
|
+
var corsHeaders = {
|
|
932
|
+
"Access-Control-Allow-Origin": "*",
|
|
933
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
934
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
935
|
+
};
|
|
936
|
+
function extractPath(request) {
|
|
937
|
+
const url = new URL(request.url);
|
|
938
|
+
const pathname = url.pathname;
|
|
939
|
+
const prefix = "/api/workflow-builder/";
|
|
940
|
+
const idx = pathname.indexOf(prefix);
|
|
941
|
+
if (idx === -1) {
|
|
942
|
+
const apiIdx = pathname.indexOf("/api/");
|
|
943
|
+
if (apiIdx === -1) return [];
|
|
944
|
+
const afterApi = pathname.slice(apiIdx + 5);
|
|
945
|
+
return afterApi.split("/").filter(Boolean);
|
|
946
|
+
}
|
|
947
|
+
const afterPrefix = pathname.slice(idx + prefix.length);
|
|
948
|
+
return afterPrefix.split("/").filter(Boolean);
|
|
949
|
+
}
|
|
950
|
+
function rebuildRequest(request, newUrl) {
|
|
951
|
+
return new Request(newUrl, {
|
|
952
|
+
method: request.method,
|
|
953
|
+
headers: request.headers,
|
|
954
|
+
body: request.body,
|
|
955
|
+
duplex: "half"
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
async function executeWorkflowBackground(executionId, workflowId, nodes, edges, input) {
|
|
959
|
+
try {
|
|
960
|
+
await executeWorkflow({
|
|
961
|
+
nodes,
|
|
962
|
+
edges,
|
|
963
|
+
triggerInput: input,
|
|
964
|
+
executionId,
|
|
965
|
+
workflowId
|
|
966
|
+
});
|
|
967
|
+
} catch (error) {
|
|
968
|
+
console.error("[Workflow Execute] Error during execution:", error);
|
|
969
|
+
await db.update(workflowExecutions).set({
|
|
970
|
+
status: "error",
|
|
971
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
972
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
973
|
+
}).where(eq2(workflowExecutions.id, executionId));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function buildWorkflowUpdateData(body) {
|
|
977
|
+
const updateData = { updatedAt: /* @__PURE__ */ new Date() };
|
|
978
|
+
if (body.name !== void 0) updateData.name = body.name;
|
|
979
|
+
if (body.description !== void 0) updateData.description = body.description;
|
|
980
|
+
if (body.nodes !== void 0) updateData.nodes = body.nodes;
|
|
981
|
+
if (body.edges !== void 0) updateData.edges = body.edges;
|
|
982
|
+
if (body.visibility !== void 0) updateData.visibility = body.visibility;
|
|
983
|
+
return updateData;
|
|
984
|
+
}
|
|
985
|
+
function sanitizeNodesForPublicView(nodes) {
|
|
986
|
+
return nodes.map((node) => {
|
|
987
|
+
const sanitizedNode = { ...node };
|
|
988
|
+
if (sanitizedNode.data && typeof sanitizedNode.data === "object" && sanitizedNode.data !== null) {
|
|
989
|
+
const data = { ...sanitizedNode.data };
|
|
990
|
+
if (data.config && typeof data.config === "object" && data.config !== null) {
|
|
991
|
+
const { integrationId: _, ...configWithoutIntegration } = data.config;
|
|
992
|
+
data.config = configWithoutIntegration;
|
|
993
|
+
}
|
|
994
|
+
sanitizedNode.data = data;
|
|
995
|
+
}
|
|
996
|
+
return sanitizedNode;
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
async function readDirectoryRecursive(dirPath, baseDir = dirPath) {
|
|
1000
|
+
const files = {};
|
|
1001
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
1002
|
+
for (const entry of entries) {
|
|
1003
|
+
const fullPath = join(dirPath, entry.name);
|
|
1004
|
+
if (entry.isDirectory()) {
|
|
1005
|
+
const subFiles = await readDirectoryRecursive(fullPath, baseDir);
|
|
1006
|
+
Object.assign(files, subFiles);
|
|
1007
|
+
} else if (entry.isFile()) {
|
|
1008
|
+
const content = await readFile(fullPath, "utf-8");
|
|
1009
|
+
const relativePath = fullPath.substring(baseDir.length + 1);
|
|
1010
|
+
files[relativePath] = content;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return files;
|
|
1014
|
+
}
|
|
1015
|
+
function generateWorkflowFiles(workflow) {
|
|
1016
|
+
const files = {};
|
|
1017
|
+
const baseName = workflow.name.replace(NON_ALPHANUMERIC_REGEX, "").split(WHITESPACE_SPLIT_REGEX).map((word, i) => {
|
|
1018
|
+
if (i === 0) {
|
|
1019
|
+
return word.toLowerCase();
|
|
1020
|
+
}
|
|
1021
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
1022
|
+
}).join("") || "execute";
|
|
1023
|
+
const functionName = `${baseName}Workflow`;
|
|
1024
|
+
const workflowCode = generateWorkflowModule(
|
|
1025
|
+
workflow.name,
|
|
1026
|
+
workflow.nodes,
|
|
1027
|
+
workflow.edges,
|
|
1028
|
+
{ functionName }
|
|
1029
|
+
);
|
|
1030
|
+
const fileName = sanitizeFileName(workflow.name);
|
|
1031
|
+
files[`workflows/${fileName}.ts`] = workflowCode;
|
|
1032
|
+
files[`app/api/workflows/${fileName}/route.ts`] = `import { start } from 'workflow/api';
|
|
1033
|
+
import { ${functionName} } from '@/workflows/${fileName}';
|
|
1034
|
+
import { NextResponse } from 'next/server';
|
|
1035
|
+
|
|
1036
|
+
export async function POST(request: Request) {
|
|
1037
|
+
try {
|
|
1038
|
+
const body = await request.json();
|
|
1039
|
+
|
|
1040
|
+
// Start the workflow execution
|
|
1041
|
+
await start(${functionName}, [body]);
|
|
1042
|
+
|
|
1043
|
+
return NextResponse.json({
|
|
1044
|
+
success: true,
|
|
1045
|
+
message: 'Workflow started successfully',
|
|
1046
|
+
});
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
return NextResponse.json(
|
|
1049
|
+
{
|
|
1050
|
+
success: false,
|
|
1051
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
1052
|
+
},
|
|
1053
|
+
{ status: 500 }
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
`;
|
|
1058
|
+
files["app/page.tsx"] = `export default function Home() {
|
|
1059
|
+
return (
|
|
1060
|
+
<main className="p-8">
|
|
1061
|
+
<h1 className="text-2xl font-bold mb-4">Workflow: ${workflow.name}</h1>
|
|
1062
|
+
<p className="mb-4 text-gray-600">API endpoint:</p>
|
|
1063
|
+
<ul className="list-disc pl-6 space-y-2">
|
|
1064
|
+
<li>
|
|
1065
|
+
<a href="/api/workflows/${fileName}" className="text-blue-600 hover:underline">
|
|
1066
|
+
/api/workflows/${fileName}
|
|
1067
|
+
</a>
|
|
1068
|
+
</li>
|
|
1069
|
+
</ul>
|
|
1070
|
+
</main>
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
`;
|
|
1074
|
+
return files;
|
|
1075
|
+
}
|
|
1076
|
+
function getIntegrationDependencies(nodes) {
|
|
1077
|
+
const actionTypes = nodes.filter((node) => node.data.type === "action").map((node) => node.data.config?.actionType).filter(Boolean);
|
|
1078
|
+
return getDependenciesForActions(actionTypes);
|
|
1079
|
+
}
|
|
1080
|
+
function generateEnvExample() {
|
|
1081
|
+
const lines = ["# Add your environment variables here"];
|
|
1082
|
+
lines.push("");
|
|
1083
|
+
lines.push("# For database integrations");
|
|
1084
|
+
lines.push("DATABASE_URL=your_database_url");
|
|
1085
|
+
const envVars = getAllEnvVars();
|
|
1086
|
+
const groupedByPrefix = {};
|
|
1087
|
+
for (const envVar of envVars) {
|
|
1088
|
+
const prefix = envVar.name.split("_")[0];
|
|
1089
|
+
if (!groupedByPrefix[prefix]) {
|
|
1090
|
+
groupedByPrefix[prefix] = [];
|
|
1091
|
+
}
|
|
1092
|
+
groupedByPrefix[prefix].push(envVar);
|
|
1093
|
+
}
|
|
1094
|
+
for (const [prefix, vars] of Object.entries(groupedByPrefix)) {
|
|
1095
|
+
lines.push(
|
|
1096
|
+
`# For ${prefix.charAt(0) + prefix.slice(1).toLowerCase()} integration`
|
|
1097
|
+
);
|
|
1098
|
+
for (const v of vars) {
|
|
1099
|
+
lines.push(`${v.name}=your_${v.name.toLowerCase()}`);
|
|
1100
|
+
}
|
|
1101
|
+
lines.push("");
|
|
1102
|
+
}
|
|
1103
|
+
return lines.join("\n");
|
|
1104
|
+
}
|
|
1105
|
+
function sanitizeFileName(name) {
|
|
1106
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// src/server/api/auth.ts
|
|
1110
|
+
var { GET: authGet, POST: authPost } = toNextJsHandler(auth);
|
|
1111
|
+
async function handleAuth(request, pathSegments) {
|
|
1112
|
+
const authPath = "/api/auth/" + pathSegments.slice(1).join("/");
|
|
1113
|
+
const url = new URL(request.url);
|
|
1114
|
+
const newUrl = `${url.origin}${authPath}${url.search}`;
|
|
1115
|
+
const newRequest = rebuildRequest(request, newUrl);
|
|
1116
|
+
if (request.method === "GET")
|
|
1117
|
+
return authGet(newRequest);
|
|
1118
|
+
return authPost(newRequest);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/server/api/integrations.ts
|
|
1122
|
+
import { NextResponse as NextResponse2 } from "next/server";
|
|
1123
|
+
async function handleGetIntegrations(request) {
|
|
1124
|
+
try {
|
|
1125
|
+
const user = await resolveUser(request);
|
|
1126
|
+
if (!user) return NextResponse2.json({ error: "Unauthorized" }, { status: 401 });
|
|
1127
|
+
const { searchParams } = new URL(request.url);
|
|
1128
|
+
const typeFilter = searchParams.get("type");
|
|
1129
|
+
const intgList = await getIntegrations(user.id, typeFilter || void 0);
|
|
1130
|
+
return NextResponse2.json(
|
|
1131
|
+
intgList.map((i) => ({
|
|
1132
|
+
id: i.id,
|
|
1133
|
+
name: i.name,
|
|
1134
|
+
type: i.type,
|
|
1135
|
+
isManaged: i.isManaged ?? false,
|
|
1136
|
+
createdAt: i.createdAt.toISOString(),
|
|
1137
|
+
updatedAt: i.updatedAt.toISOString()
|
|
1138
|
+
}))
|
|
1139
|
+
);
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
console.error("Failed to get integrations:", error);
|
|
1142
|
+
return NextResponse2.json(
|
|
1143
|
+
{ error: "Failed to get integrations", details: error instanceof Error ? error.message : "Unknown error" },
|
|
1144
|
+
{ status: 500 }
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
async function handleCreateIntegration(request) {
|
|
1149
|
+
try {
|
|
1150
|
+
const user = await resolveUser(request);
|
|
1151
|
+
if (!user) return NextResponse2.json({ error: "Unauthorized" }, { status: 401 });
|
|
1152
|
+
const body = await request.json();
|
|
1153
|
+
if (!(body.type && body.config)) {
|
|
1154
|
+
return NextResponse2.json({ error: "Type and config are required" }, { status: 400 });
|
|
1155
|
+
}
|
|
1156
|
+
const integration = await createIntegration(
|
|
1157
|
+
user.id,
|
|
1158
|
+
body.name || "",
|
|
1159
|
+
body.type,
|
|
1160
|
+
body.config
|
|
1161
|
+
);
|
|
1162
|
+
return NextResponse2.json({
|
|
1163
|
+
id: integration.id,
|
|
1164
|
+
name: integration.name,
|
|
1165
|
+
type: integration.type,
|
|
1166
|
+
createdAt: integration.createdAt.toISOString(),
|
|
1167
|
+
updatedAt: integration.updatedAt.toISOString()
|
|
1168
|
+
});
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
console.error("Failed to create integration:", error);
|
|
1171
|
+
return NextResponse2.json(
|
|
1172
|
+
{ error: "Failed to create integration", details: error instanceof Error ? error.message : "Unknown error" },
|
|
1173
|
+
{ status: 500 }
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
async function handleGetIntegration(request, integrationId) {
|
|
1178
|
+
try {
|
|
1179
|
+
const user = await resolveUser(request);
|
|
1180
|
+
if (!user) return NextResponse2.json({ error: "Unauthorized" }, { status: 401 });
|
|
1181
|
+
const integration = await getIntegration(integrationId, user.id);
|
|
1182
|
+
if (!integration) {
|
|
1183
|
+
return NextResponse2.json({ error: "Integration not found" }, { status: 404 });
|
|
1184
|
+
}
|
|
1185
|
+
return NextResponse2.json({
|
|
1186
|
+
id: integration.id,
|
|
1187
|
+
name: integration.name,
|
|
1188
|
+
type: integration.type,
|
|
1189
|
+
config: integration.config,
|
|
1190
|
+
createdAt: integration.createdAt.toISOString(),
|
|
1191
|
+
updatedAt: integration.updatedAt.toISOString()
|
|
1192
|
+
});
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
console.error("Failed to get integration:", error);
|
|
1195
|
+
return NextResponse2.json(
|
|
1196
|
+
{ error: "Failed to get integration", details: error instanceof Error ? error.message : "Unknown error" },
|
|
1197
|
+
{ status: 500 }
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
async function handleUpdateIntegration(request, integrationId) {
|
|
1202
|
+
try {
|
|
1203
|
+
const user = await resolveUser(request);
|
|
1204
|
+
if (!user) return NextResponse2.json({ error: "Unauthorized" }, { status: 401 });
|
|
1205
|
+
const body = await request.json();
|
|
1206
|
+
const integration = await updateIntegration(integrationId, user.id, body);
|
|
1207
|
+
if (!integration) {
|
|
1208
|
+
return NextResponse2.json({ error: "Integration not found" }, { status: 404 });
|
|
1209
|
+
}
|
|
1210
|
+
return NextResponse2.json({
|
|
1211
|
+
id: integration.id,
|
|
1212
|
+
name: integration.name,
|
|
1213
|
+
type: integration.type,
|
|
1214
|
+
config: integration.config,
|
|
1215
|
+
createdAt: integration.createdAt.toISOString(),
|
|
1216
|
+
updatedAt: integration.updatedAt.toISOString()
|
|
1217
|
+
});
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
console.error("Failed to update integration:", error);
|
|
1220
|
+
return NextResponse2.json(
|
|
1221
|
+
{ error: "Failed to update integration", details: error instanceof Error ? error.message : "Unknown error" },
|
|
1222
|
+
{ status: 500 }
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
async function handleDeleteIntegration(request, integrationId) {
|
|
1227
|
+
try {
|
|
1228
|
+
const user = await resolveUser(request);
|
|
1229
|
+
if (!user) return NextResponse2.json({ error: "Unauthorized" }, { status: 401 });
|
|
1230
|
+
const success = await deleteIntegration(integrationId, user.id);
|
|
1231
|
+
if (!success) {
|
|
1232
|
+
return NextResponse2.json({ error: "Integration not found" }, { status: 404 });
|
|
1233
|
+
}
|
|
1234
|
+
return NextResponse2.json({ success: true });
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
console.error("Failed to delete integration:", error);
|
|
1237
|
+
return NextResponse2.json(
|
|
1238
|
+
{ error: "Failed to delete integration", details: error instanceof Error ? error.message : "Unknown error" },
|
|
1239
|
+
{ status: 500 }
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
async function handleTestIntegration(request, integrationId) {
|
|
1244
|
+
try {
|
|
1245
|
+
const user = await resolveUser(request);
|
|
1246
|
+
if (!user) return NextResponse2.json({ error: "Unauthorized" }, { status: 401 });
|
|
1247
|
+
const integration = await getIntegration(integrationId, user.id);
|
|
1248
|
+
if (!integration) {
|
|
1249
|
+
return NextResponse2.json({ error: "Integration not found" }, { status: 404 });
|
|
1250
|
+
}
|
|
1251
|
+
if (integration.type === "database") {
|
|
1252
|
+
return NextResponse2.json({ status: "success", message: "Integration exists" });
|
|
1253
|
+
}
|
|
1254
|
+
return NextResponse2.json({ status: "success", message: "Integration exists" });
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
console.error("Failed to test integration:", error);
|
|
1257
|
+
return NextResponse2.json(
|
|
1258
|
+
{ status: "error", message: error instanceof Error ? error.message : "Failed to test connection" },
|
|
1259
|
+
{ status: 500 }
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
async function handleTestIntegrationCredentials(request) {
|
|
1264
|
+
try {
|
|
1265
|
+
const user = await resolveUser(request);
|
|
1266
|
+
if (!user) return NextResponse2.json({ error: "Unauthorized" }, { status: 401 });
|
|
1267
|
+
const body = await request.json();
|
|
1268
|
+
if (!(body.type && body.config)) {
|
|
1269
|
+
return NextResponse2.json({ error: "Type and config are required" }, { status: 400 });
|
|
1270
|
+
}
|
|
1271
|
+
return NextResponse2.json({ status: "success", message: "Credentials accepted" });
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
console.error("Failed to test credentials:", error);
|
|
1274
|
+
return NextResponse2.json(
|
|
1275
|
+
{ status: "error", message: error instanceof Error ? error.message : "Failed to test connection" },
|
|
1276
|
+
{ status: 500 }
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// src/server/api/users.ts
|
|
1282
|
+
import { eq as eq3 } from "drizzle-orm";
|
|
1283
|
+
import { NextResponse as NextResponse3 } from "next/server";
|
|
1284
|
+
async function handleGetUser(request) {
|
|
1285
|
+
try {
|
|
1286
|
+
const user = await resolveUser(request);
|
|
1287
|
+
if (!user) return NextResponse3.json({ error: "Unauthorized" }, { status: 401 });
|
|
1288
|
+
const userData = await db.query.users.findFirst({
|
|
1289
|
+
where: eq3(users.id, user.id),
|
|
1290
|
+
columns: {
|
|
1291
|
+
id: true,
|
|
1292
|
+
name: true,
|
|
1293
|
+
email: true,
|
|
1294
|
+
image: true,
|
|
1295
|
+
isAnonymous: true
|
|
1296
|
+
}
|
|
1297
|
+
});
|
|
1298
|
+
if (!userData) {
|
|
1299
|
+
return NextResponse3.json({ error: "User not found" }, { status: 404 });
|
|
1300
|
+
}
|
|
1301
|
+
const userAccount = await db.query.accounts.findFirst({
|
|
1302
|
+
where: eq3(accounts.userId, user.id),
|
|
1303
|
+
columns: { providerId: true }
|
|
1304
|
+
});
|
|
1305
|
+
return NextResponse3.json({
|
|
1306
|
+
...userData,
|
|
1307
|
+
providerId: userAccount?.providerId ?? null
|
|
1308
|
+
});
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
console.error("Failed to get user:", error);
|
|
1311
|
+
return NextResponse3.json(
|
|
1312
|
+
{ error: error instanceof Error ? error.message : "Failed to get user" },
|
|
1313
|
+
{ status: 500 }
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
async function handleUpdateUser(request) {
|
|
1318
|
+
try {
|
|
1319
|
+
const user = await resolveUser(request);
|
|
1320
|
+
if (!user) return NextResponse3.json({ error: "Unauthorized" }, { status: 401 });
|
|
1321
|
+
const userAccount = await db.query.accounts.findFirst({
|
|
1322
|
+
where: eq3(accounts.userId, user.id),
|
|
1323
|
+
columns: { providerId: true }
|
|
1324
|
+
});
|
|
1325
|
+
const oauthProviders = ["vercel", "github", "google"];
|
|
1326
|
+
if (userAccount && oauthProviders.includes(userAccount.providerId)) {
|
|
1327
|
+
return NextResponse3.json(
|
|
1328
|
+
{ error: "Cannot update profile for OAuth users" },
|
|
1329
|
+
{ status: 403 }
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
const body = await request.json();
|
|
1333
|
+
const updates = {};
|
|
1334
|
+
if (body.name !== void 0) updates.name = body.name;
|
|
1335
|
+
if (body.email !== void 0) updates.email = body.email;
|
|
1336
|
+
await db.update(users).set(updates).where(eq3(users.id, user.id));
|
|
1337
|
+
return NextResponse3.json({ success: true });
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
console.error("Failed to update user:", error);
|
|
1340
|
+
return NextResponse3.json(
|
|
1341
|
+
{ error: error instanceof Error ? error.message : "Failed to update user" },
|
|
1342
|
+
{ status: 500 }
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// src/server/api/workflows.ts
|
|
1348
|
+
import { and as and2, desc, eq as eq4, inArray } from "drizzle-orm";
|
|
1349
|
+
import { NextResponse as NextResponse4 } from "next/server";
|
|
1350
|
+
import { createHash as createHash2 } from "crypto";
|
|
1351
|
+
|
|
1352
|
+
// src/client/lib/workflow-codegen-sdk.ts
|
|
1353
|
+
import "server-only";
|
|
1354
|
+
var SYSTEM_CODEGEN_TEMPLATES = {
|
|
1355
|
+
"Database Query": databaseQueryAction.codeGenerator,
|
|
1356
|
+
"HTTP Request": httpRequestAction.codeGenerator,
|
|
1357
|
+
Condition: conditionAction.codeGenerator
|
|
1358
|
+
};
|
|
1359
|
+
var FUNCTION_BODY_REGEX = /export\s+(?:async\s+)?function\s+\w+\s*\([^)]*\)\s*(?::\s*[^{]+)?\s*\{([\s\S]*)\}/;
|
|
1360
|
+
function loadStepImplementation(actionType) {
|
|
1361
|
+
let template = SYSTEM_CODEGEN_TEMPLATES[actionType];
|
|
1362
|
+
if (!template) {
|
|
1363
|
+
const action = findActionById(actionType);
|
|
1364
|
+
if (action?.codegenTemplate) {
|
|
1365
|
+
template = action.codegenTemplate;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (!template) {
|
|
1369
|
+
return null;
|
|
1370
|
+
}
|
|
1371
|
+
try {
|
|
1372
|
+
const functionMatch = template.match(FUNCTION_BODY_REGEX);
|
|
1373
|
+
if (functionMatch) {
|
|
1374
|
+
return functionMatch[1].trim();
|
|
1375
|
+
}
|
|
1376
|
+
return null;
|
|
1377
|
+
} catch {
|
|
1378
|
+
return null;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
function processNewFormatID(trimmed, match) {
|
|
1382
|
+
const withoutAt = trimmed.substring(1);
|
|
1383
|
+
const colonIndex = withoutAt.indexOf(":");
|
|
1384
|
+
if (colonIndex === -1) {
|
|
1385
|
+
return match;
|
|
1386
|
+
}
|
|
1387
|
+
const nodeId = withoutAt.substring(0, colonIndex);
|
|
1388
|
+
const rest = withoutAt.substring(colonIndex + 1);
|
|
1389
|
+
const dotIndex = rest.indexOf(".");
|
|
1390
|
+
const fieldPath = dotIndex !== -1 ? rest.substring(dotIndex + 1) : "";
|
|
1391
|
+
const sanitizedNodeId = nodeId.replace(/[^a-zA-Z0-9]/g, "_");
|
|
1392
|
+
if (!fieldPath) {
|
|
1393
|
+
return `\${outputs?.['${sanitizedNodeId}']?.data}`;
|
|
1394
|
+
}
|
|
1395
|
+
const accessPath = fieldPath.split(".").map((part) => {
|
|
1396
|
+
const arrayMatch = part.match(ARRAY_INDEX_PATTERN);
|
|
1397
|
+
if (arrayMatch) {
|
|
1398
|
+
return `?.${arrayMatch[1]}?.[${arrayMatch[2]}]`;
|
|
1399
|
+
}
|
|
1400
|
+
return `?.${part}`;
|
|
1401
|
+
}).join("");
|
|
1402
|
+
return `\${outputs?.['${sanitizedNodeId}']?.data${accessPath}}`;
|
|
1403
|
+
}
|
|
1404
|
+
function processLegacyDollarRef(trimmed) {
|
|
1405
|
+
const withoutDollar = trimmed.substring(1);
|
|
1406
|
+
if (!(withoutDollar.includes(".") || withoutDollar.includes("["))) {
|
|
1407
|
+
const sanitizedNodeId2 = withoutDollar.replace(/[^a-zA-Z0-9]/g, "_");
|
|
1408
|
+
return `\${outputs?.['${sanitizedNodeId2}']?.data}`;
|
|
1409
|
+
}
|
|
1410
|
+
const parts = withoutDollar.split(".");
|
|
1411
|
+
const nodeId = parts[0];
|
|
1412
|
+
const sanitizedNodeId = nodeId.replace(/[^a-zA-Z0-9]/g, "_");
|
|
1413
|
+
const fieldPath = parts.slice(1).join(".");
|
|
1414
|
+
if (!fieldPath) {
|
|
1415
|
+
return `\${outputs?.['${sanitizedNodeId}']?.data}`;
|
|
1416
|
+
}
|
|
1417
|
+
const accessPath = fieldPath.split(".").map((part) => {
|
|
1418
|
+
const arrayMatch = part.match(ARRAY_INDEX_PATTERN);
|
|
1419
|
+
if (arrayMatch) {
|
|
1420
|
+
return `?.${arrayMatch[1]}?.[${arrayMatch[2]}]`;
|
|
1421
|
+
}
|
|
1422
|
+
return `?.${part}`;
|
|
1423
|
+
}).join("");
|
|
1424
|
+
return `\${outputs?.['${sanitizedNodeId}']?.data${accessPath}}`;
|
|
1425
|
+
}
|
|
1426
|
+
function convertTemplateToJS(template) {
|
|
1427
|
+
if (!template || typeof template !== "string") {
|
|
1428
|
+
return template;
|
|
1429
|
+
}
|
|
1430
|
+
const pattern = /\{\{([^}]+)\}\}/g;
|
|
1431
|
+
return template.replace(pattern, (match, expression) => {
|
|
1432
|
+
const trimmed = expression.trim();
|
|
1433
|
+
if (trimmed.startsWith("@")) {
|
|
1434
|
+
return processNewFormatID(trimmed, match);
|
|
1435
|
+
}
|
|
1436
|
+
if (trimmed.startsWith("$")) {
|
|
1437
|
+
return processLegacyDollarRef(trimmed);
|
|
1438
|
+
}
|
|
1439
|
+
return match;
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
function analyzeNodeUsageSDK(nodes) {
|
|
1443
|
+
const usedNodes = analyzeNodeUsage(nodes);
|
|
1444
|
+
const lastNode = nodes.at(-1);
|
|
1445
|
+
if (lastNode) {
|
|
1446
|
+
usedNodes.add(lastNode.id);
|
|
1447
|
+
}
|
|
1448
|
+
return usedNodes;
|
|
1449
|
+
}
|
|
1450
|
+
function createStepNameMapping(nodes) {
|
|
1451
|
+
const stepNameCounts = /* @__PURE__ */ new Map();
|
|
1452
|
+
const nodeToStepName = /* @__PURE__ */ new Map();
|
|
1453
|
+
for (const node of nodes) {
|
|
1454
|
+
if (node.data.type === "action") {
|
|
1455
|
+
const config = node.data.config || {};
|
|
1456
|
+
const actionType = config.actionType;
|
|
1457
|
+
const baseLabel = node.data.label || actionType || "UnnamedStep";
|
|
1458
|
+
const baseName = sanitizeStepName(baseLabel);
|
|
1459
|
+
const count = stepNameCounts.get(baseName) || 0;
|
|
1460
|
+
stepNameCounts.set(baseName, count + 1);
|
|
1461
|
+
const uniqueName = count > 0 ? `${baseName}${count + 1}` : baseName;
|
|
1462
|
+
nodeToStepName.set(node.id, uniqueName);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
return nodeToStepName;
|
|
1466
|
+
}
|
|
1467
|
+
function generateAllStepFunctions(nodes, nodeToStepName, generateStepFunc) {
|
|
1468
|
+
const stepFunctions = [];
|
|
1469
|
+
for (const node of nodes) {
|
|
1470
|
+
if (node.data.type === "action") {
|
|
1471
|
+
const uniqueName = nodeToStepName.get(node.id);
|
|
1472
|
+
stepFunctions.push(generateStepFunc(node, uniqueName));
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
return stepFunctions;
|
|
1476
|
+
}
|
|
1477
|
+
function generateWorkflowSDKCode(workflowName, nodes, edges) {
|
|
1478
|
+
const imports = /* @__PURE__ */ new Set();
|
|
1479
|
+
const stepFunctions = [];
|
|
1480
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
1481
|
+
const edgesBySource = buildEdgeMap(edges);
|
|
1482
|
+
const triggerNodes = findTriggerNodes(nodes, edges);
|
|
1483
|
+
const usedNodeOutputs = analyzeNodeUsageSDK(nodes);
|
|
1484
|
+
imports.add("import { sleep, FatalError } from 'workflow';");
|
|
1485
|
+
function buildEmailParams(config) {
|
|
1486
|
+
imports.add("import { Resend } from 'resend';");
|
|
1487
|
+
return [
|
|
1488
|
+
`fromEmail: process.env.RESEND_FROM_EMAIL || 'noreply@example.com'`,
|
|
1489
|
+
`emailTo: \`${convertTemplateToJS(config.emailTo || "user@example.com")}\``,
|
|
1490
|
+
`emailSubject: \`${convertTemplateToJS(config.emailSubject || "Notification")}\``,
|
|
1491
|
+
`emailBody: \`${convertTemplateToJS(config.emailBody || "No content")}\``,
|
|
1492
|
+
"apiKey: process.env.RESEND_API_KEY!"
|
|
1493
|
+
];
|
|
1494
|
+
}
|
|
1495
|
+
function buildSlackParams(config) {
|
|
1496
|
+
imports.add("import { WebClient } from '@slack/web-api';");
|
|
1497
|
+
return [
|
|
1498
|
+
`slackChannel: \`${convertTemplateToJS(config.slackChannel || "#general")}\``,
|
|
1499
|
+
`slackMessage: \`${convertTemplateToJS(config.slackMessage || "No message")}\``,
|
|
1500
|
+
"apiKey: process.env.SLACK_API_KEY!"
|
|
1501
|
+
];
|
|
1502
|
+
}
|
|
1503
|
+
function buildTicketParams(config) {
|
|
1504
|
+
imports.add("import { LinearClient } from '@linear/sdk';");
|
|
1505
|
+
const params = [
|
|
1506
|
+
`ticketTitle: \`${convertTemplateToJS(config.ticketTitle || "New Issue")}\``,
|
|
1507
|
+
`ticketDescription: \`${convertTemplateToJS(config.ticketDescription || "")}\``,
|
|
1508
|
+
"apiKey: process.env.LINEAR_API_KEY!"
|
|
1509
|
+
];
|
|
1510
|
+
if (config.teamId) {
|
|
1511
|
+
params.push(`teamId: "${config.teamId}"`);
|
|
1512
|
+
}
|
|
1513
|
+
return params;
|
|
1514
|
+
}
|
|
1515
|
+
function buildAITextParams(config) {
|
|
1516
|
+
imports.add("import { generateText } from 'ai';");
|
|
1517
|
+
const modelId = config.aiModel || "meta/llama-4-scout";
|
|
1518
|
+
let modelString;
|
|
1519
|
+
if (modelId.includes("/")) {
|
|
1520
|
+
modelString = modelId;
|
|
1521
|
+
} else {
|
|
1522
|
+
let provider;
|
|
1523
|
+
if (modelId.startsWith("gpt-") || modelId.startsWith("o1-")) {
|
|
1524
|
+
provider = "openai";
|
|
1525
|
+
} else if (modelId.startsWith("claude-")) {
|
|
1526
|
+
provider = "anthropic";
|
|
1527
|
+
} else {
|
|
1528
|
+
provider = "openai";
|
|
1529
|
+
}
|
|
1530
|
+
modelString = `${provider}/${modelId}`;
|
|
1531
|
+
}
|
|
1532
|
+
return [
|
|
1533
|
+
`model: "${modelString}"`,
|
|
1534
|
+
`prompt: \`${convertTemplateToJS(config.aiPrompt || "")}\``,
|
|
1535
|
+
"apiKey: process.env.OPENAI_API_KEY!"
|
|
1536
|
+
];
|
|
1537
|
+
}
|
|
1538
|
+
function buildAIImageParams(config) {
|
|
1539
|
+
imports.add(
|
|
1540
|
+
"import { experimental_generateImage as generateImage } from 'ai';"
|
|
1541
|
+
);
|
|
1542
|
+
const imageModel = config.imageModel || "google/imagen-4.0-generate";
|
|
1543
|
+
return [
|
|
1544
|
+
`model: "${imageModel}"`,
|
|
1545
|
+
`prompt: \`${convertTemplateToJS(config.imagePrompt || "")}\``,
|
|
1546
|
+
'size: "1024x1024"',
|
|
1547
|
+
"providerOptions: { openai: { apiKey: process.env.AI_GATEWAY_API_KEY! } }"
|
|
1548
|
+
];
|
|
1549
|
+
}
|
|
1550
|
+
function buildDatabaseParams(config) {
|
|
1551
|
+
return [
|
|
1552
|
+
`query: \`${convertTemplateToJS(config.dbQuery || "SELECT 1")}\``
|
|
1553
|
+
];
|
|
1554
|
+
}
|
|
1555
|
+
function buildHttpParams(config) {
|
|
1556
|
+
const params = [
|
|
1557
|
+
`url: "${config.endpoint || "https://api.example.com/endpoint"}"`,
|
|
1558
|
+
`method: "${config.httpMethod || "POST"}"`,
|
|
1559
|
+
`headers: ${config.httpHeaders || "{}"}`
|
|
1560
|
+
];
|
|
1561
|
+
if (config.httpBody) {
|
|
1562
|
+
params.push(`body: ${config.httpBody}`);
|
|
1563
|
+
}
|
|
1564
|
+
return params;
|
|
1565
|
+
}
|
|
1566
|
+
function buildConditionParams(config) {
|
|
1567
|
+
return [
|
|
1568
|
+
`condition: ${convertTemplateToJS(config.condition || "true")}`
|
|
1569
|
+
];
|
|
1570
|
+
}
|
|
1571
|
+
function buildFirecrawlParams(actionType, config) {
|
|
1572
|
+
imports.add("import FirecrawlApp from '@mendable/firecrawl-js';");
|
|
1573
|
+
const mode = actionType === "Search" ? "search" : "scrape";
|
|
1574
|
+
const formats = config.formats ? JSON.stringify(config.formats) : "['markdown']";
|
|
1575
|
+
const params = [
|
|
1576
|
+
`mode: '${mode}'`,
|
|
1577
|
+
"apiKey: process.env.FIRECRAWL_API_KEY!",
|
|
1578
|
+
`formats: ${formats}`
|
|
1579
|
+
];
|
|
1580
|
+
if (config.url) {
|
|
1581
|
+
params.push(
|
|
1582
|
+
`url: \`${convertTemplateToJS(config.url || "")}\``
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
if (config.query) {
|
|
1586
|
+
params.push(
|
|
1587
|
+
`query: \`${convertTemplateToJS(config.query || "")}\``
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
if (config.limit) {
|
|
1591
|
+
params.push(`limit: ${config.limit}`);
|
|
1592
|
+
}
|
|
1593
|
+
return params;
|
|
1594
|
+
}
|
|
1595
|
+
function buildV0CreateChatParams(config) {
|
|
1596
|
+
imports.add("import { createClient } from 'v0-sdk';");
|
|
1597
|
+
const params = [
|
|
1598
|
+
`message: \`${convertTemplateToJS(config.message || "")}\``,
|
|
1599
|
+
"apiKey: process.env.V0_API_KEY!"
|
|
1600
|
+
];
|
|
1601
|
+
if (config.system) {
|
|
1602
|
+
params.push(
|
|
1603
|
+
`system: \`${convertTemplateToJS(config.system || "")}\``
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
return params;
|
|
1607
|
+
}
|
|
1608
|
+
function buildV0SendMessageParams(config) {
|
|
1609
|
+
imports.add("import { createClient } from 'v0-sdk';");
|
|
1610
|
+
return [
|
|
1611
|
+
`chatId: \`${convertTemplateToJS(config.chatId || "")}\``,
|
|
1612
|
+
`message: \`${convertTemplateToJS(config.message || "")}\``,
|
|
1613
|
+
"apiKey: process.env.V0_API_KEY!"
|
|
1614
|
+
];
|
|
1615
|
+
}
|
|
1616
|
+
function buildClerkGetUserParams(config) {
|
|
1617
|
+
return [
|
|
1618
|
+
`userId: \`${convertTemplateToJS(config.userId || "")}\``
|
|
1619
|
+
];
|
|
1620
|
+
}
|
|
1621
|
+
function buildClerkCreateUserParams(config) {
|
|
1622
|
+
const params = [
|
|
1623
|
+
`emailAddress: \`${convertTemplateToJS(config.emailAddress || "")}\``
|
|
1624
|
+
];
|
|
1625
|
+
if (config.password) {
|
|
1626
|
+
params.push(
|
|
1627
|
+
`password: \`${convertTemplateToJS(config.password)}\``
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
if (config.firstName) {
|
|
1631
|
+
params.push(
|
|
1632
|
+
`firstName: \`${convertTemplateToJS(config.firstName)}\``
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1635
|
+
if (config.lastName) {
|
|
1636
|
+
params.push(
|
|
1637
|
+
`lastName: \`${convertTemplateToJS(config.lastName)}\``
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
if (config.publicMetadata) {
|
|
1641
|
+
params.push(
|
|
1642
|
+
`publicMetadata: \`${convertTemplateToJS(config.publicMetadata)}\``
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
if (config.privateMetadata) {
|
|
1646
|
+
params.push(
|
|
1647
|
+
`privateMetadata: \`${convertTemplateToJS(config.privateMetadata)}\``
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
return params;
|
|
1651
|
+
}
|
|
1652
|
+
function buildClerkUpdateUserParams(config) {
|
|
1653
|
+
const params = [
|
|
1654
|
+
`userId: \`${convertTemplateToJS(config.userId || "")}\``
|
|
1655
|
+
];
|
|
1656
|
+
if (config.firstName) {
|
|
1657
|
+
params.push(
|
|
1658
|
+
`firstName: \`${convertTemplateToJS(config.firstName)}\``
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
if (config.lastName) {
|
|
1662
|
+
params.push(
|
|
1663
|
+
`lastName: \`${convertTemplateToJS(config.lastName)}\``
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
if (config.publicMetadata) {
|
|
1667
|
+
params.push(
|
|
1668
|
+
`publicMetadata: \`${convertTemplateToJS(config.publicMetadata)}\``
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
if (config.privateMetadata) {
|
|
1672
|
+
params.push(
|
|
1673
|
+
`privateMetadata: \`${convertTemplateToJS(config.privateMetadata)}\``
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
return params;
|
|
1677
|
+
}
|
|
1678
|
+
function buildClerkDeleteUserParams(config) {
|
|
1679
|
+
return [
|
|
1680
|
+
`userId: \`${convertTemplateToJS(config.userId || "")}\``
|
|
1681
|
+
];
|
|
1682
|
+
}
|
|
1683
|
+
function buildStepInputParams(actionType, config) {
|
|
1684
|
+
const paramBuilders = {
|
|
1685
|
+
"Send Email": () => buildEmailParams(config),
|
|
1686
|
+
"Send Slack Message": () => buildSlackParams(config),
|
|
1687
|
+
"Create Ticket": () => buildTicketParams(config),
|
|
1688
|
+
"Generate Text": () => buildAITextParams(config),
|
|
1689
|
+
"Generate Image": () => buildAIImageParams(config),
|
|
1690
|
+
"Database Query": () => buildDatabaseParams(config),
|
|
1691
|
+
"HTTP Request": () => buildHttpParams(config),
|
|
1692
|
+
Condition: () => buildConditionParams(config),
|
|
1693
|
+
Scrape: () => buildFirecrawlParams(actionType, config),
|
|
1694
|
+
Search: () => buildFirecrawlParams(actionType, config),
|
|
1695
|
+
"Create Chat": () => buildV0CreateChatParams(config),
|
|
1696
|
+
"Send Message": () => buildV0SendMessageParams(config),
|
|
1697
|
+
// Clerk
|
|
1698
|
+
"Get User": () => buildClerkGetUserParams(config),
|
|
1699
|
+
"Create User": () => buildClerkCreateUserParams(config),
|
|
1700
|
+
"Update User": () => buildClerkUpdateUserParams(config),
|
|
1701
|
+
"Delete User": () => buildClerkDeleteUserParams(config)
|
|
1702
|
+
};
|
|
1703
|
+
const builder = paramBuilders[actionType];
|
|
1704
|
+
return builder ? builder() : [];
|
|
1705
|
+
}
|
|
1706
|
+
function generateStepFunction(node, uniqueStepName) {
|
|
1707
|
+
const config = node.data.config || {};
|
|
1708
|
+
const actionType = config.actionType;
|
|
1709
|
+
const label = node.data.label || actionType || "UnnamedStep";
|
|
1710
|
+
const stepName = uniqueStepName || sanitizeStepName(label);
|
|
1711
|
+
const stepImplementation = loadStepImplementation(actionType);
|
|
1712
|
+
let stepBody;
|
|
1713
|
+
if (stepImplementation && node.data.type === "action") {
|
|
1714
|
+
const inputParams = buildStepInputParams(actionType, config);
|
|
1715
|
+
stepBody = ` // Call step function with constructed input
|
|
1716
|
+
const stepInput = {
|
|
1717
|
+
${inputParams.join(",\n ")}
|
|
1718
|
+
};
|
|
1719
|
+
|
|
1720
|
+
// Execute step implementation
|
|
1721
|
+
${stepImplementation}`;
|
|
1722
|
+
} else {
|
|
1723
|
+
stepBody = " return { success: true };";
|
|
1724
|
+
}
|
|
1725
|
+
return `async function ${stepName}(input: Record<string, unknown> & { outputs?: Record<string, { label: string; data: unknown }> }) {
|
|
1726
|
+
"use step";
|
|
1727
|
+
|
|
1728
|
+
${stepBody}
|
|
1729
|
+
}`;
|
|
1730
|
+
}
|
|
1731
|
+
const nodeToStepName = createStepNameMapping(nodes);
|
|
1732
|
+
stepFunctions.push(
|
|
1733
|
+
...generateAllStepFunctions(nodes, nodeToStepName, generateStepFunction)
|
|
1734
|
+
);
|
|
1735
|
+
function generateTriggerCode(nodeId, label, indent) {
|
|
1736
|
+
if (!usedNodeOutputs.has(nodeId)) {
|
|
1737
|
+
return [`${indent}// Trigger (outputs not used)`];
|
|
1738
|
+
}
|
|
1739
|
+
const varName = `result_${sanitizeVarName(nodeId)}`;
|
|
1740
|
+
return [
|
|
1741
|
+
`${indent}// Triggered`,
|
|
1742
|
+
`${indent}let ${varName} = input;`,
|
|
1743
|
+
`${indent}outputs['${sanitizeVarName(nodeId)}'] = { label: '${label}', data: ${varName} };`
|
|
1744
|
+
];
|
|
1745
|
+
}
|
|
1746
|
+
function generateActionTransformCode(nodeId, nodeConfig, label, indent) {
|
|
1747
|
+
const nodeActionType = nodeConfig.actionType;
|
|
1748
|
+
const nodeLabel = label || nodeActionType || "UnnamedStep";
|
|
1749
|
+
const stepFnName = nodeToStepName.get(nodeId) || sanitizeStepName(nodeLabel);
|
|
1750
|
+
const lines = [`${indent}// ${nodeLabel}`];
|
|
1751
|
+
const outputIsUsed = usedNodeOutputs.has(nodeId);
|
|
1752
|
+
if (outputIsUsed) {
|
|
1753
|
+
const varName = `result_${sanitizeVarName(nodeId)}`;
|
|
1754
|
+
lines.push(
|
|
1755
|
+
`${indent}const ${varName} = await ${stepFnName}({ ...input, outputs });`
|
|
1756
|
+
);
|
|
1757
|
+
lines.push(
|
|
1758
|
+
`${indent}outputs['${sanitizeVarName(nodeId)}'] = { label: '${nodeLabel}', data: ${varName} };`
|
|
1759
|
+
);
|
|
1760
|
+
} else {
|
|
1761
|
+
lines.push(`${indent}await ${stepFnName}({ ...input, outputs });`);
|
|
1762
|
+
}
|
|
1763
|
+
return lines;
|
|
1764
|
+
}
|
|
1765
|
+
function generateConditionCode(nodeId, node, indent, visitedLocal) {
|
|
1766
|
+
const condition = node.data.config?.condition || "true";
|
|
1767
|
+
const convertedCondition = convertTemplateToJS(condition);
|
|
1768
|
+
const nextNodes = edgesBySource.get(nodeId) || [];
|
|
1769
|
+
const conditionVarName = `conditionValue_${sanitizeVarName(nodeId)}`;
|
|
1770
|
+
const lines = [];
|
|
1771
|
+
if (nextNodes.length > 0) {
|
|
1772
|
+
lines.push(`${indent}// ${node.data.label}`);
|
|
1773
|
+
lines.push(
|
|
1774
|
+
`${indent}const ${conditionVarName} = \`${escapeForTemplateLiteral(convertedCondition)}\`;`
|
|
1775
|
+
);
|
|
1776
|
+
lines.push(`${indent}if (${conditionVarName}) {`);
|
|
1777
|
+
if (nextNodes[0]) {
|
|
1778
|
+
lines.push(
|
|
1779
|
+
...generateWorkflowBody(nextNodes[0], `${indent} `, visitedLocal)
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
if (nextNodes[1]) {
|
|
1783
|
+
lines.push(`${indent}} else {`);
|
|
1784
|
+
lines.push(
|
|
1785
|
+
...generateWorkflowBody(nextNodes[1], `${indent} `, visitedLocal)
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
lines.push(`${indent}}`);
|
|
1789
|
+
}
|
|
1790
|
+
return lines;
|
|
1791
|
+
}
|
|
1792
|
+
function generateWorkflowBody(nodeId, indent = " ", visitedLocal = /* @__PURE__ */ new Set()) {
|
|
1793
|
+
if (visitedLocal.has(nodeId)) {
|
|
1794
|
+
return [];
|
|
1795
|
+
}
|
|
1796
|
+
visitedLocal.add(nodeId);
|
|
1797
|
+
const node = nodeMap.get(nodeId);
|
|
1798
|
+
if (!node) {
|
|
1799
|
+
return [];
|
|
1800
|
+
}
|
|
1801
|
+
const lines = [];
|
|
1802
|
+
switch (node.data.type) {
|
|
1803
|
+
case "trigger":
|
|
1804
|
+
lines.push(...generateTriggerCode(nodeId, node.data.label, indent));
|
|
1805
|
+
break;
|
|
1806
|
+
case "action": {
|
|
1807
|
+
const actionType = node.data.config?.actionType;
|
|
1808
|
+
if (actionType === "Condition") {
|
|
1809
|
+
lines.push(
|
|
1810
|
+
...generateConditionCode(nodeId, node, indent, visitedLocal)
|
|
1811
|
+
);
|
|
1812
|
+
return lines;
|
|
1813
|
+
}
|
|
1814
|
+
lines.push(
|
|
1815
|
+
...generateActionTransformCode(
|
|
1816
|
+
nodeId,
|
|
1817
|
+
node.data.config || {},
|
|
1818
|
+
node.data.label,
|
|
1819
|
+
indent
|
|
1820
|
+
)
|
|
1821
|
+
);
|
|
1822
|
+
break;
|
|
1823
|
+
}
|
|
1824
|
+
default:
|
|
1825
|
+
lines.push(`${indent}// Unknown node type: ${node.data.type}`);
|
|
1826
|
+
break;
|
|
1827
|
+
}
|
|
1828
|
+
const nextNodes = edgesBySource.get(nodeId) || [];
|
|
1829
|
+
for (const nextNodeId of nextNodes) {
|
|
1830
|
+
lines.push(...generateWorkflowBody(nextNodeId, indent, visitedLocal));
|
|
1831
|
+
}
|
|
1832
|
+
return lines;
|
|
1833
|
+
}
|
|
1834
|
+
const workflowBody = [];
|
|
1835
|
+
if (triggerNodes.length === 0) {
|
|
1836
|
+
workflowBody.push(' return { error: "No trigger nodes" };');
|
|
1837
|
+
} else {
|
|
1838
|
+
workflowBody.push(
|
|
1839
|
+
" // Track outputs from each node for template processing"
|
|
1840
|
+
);
|
|
1841
|
+
workflowBody.push(
|
|
1842
|
+
" const outputs: Record<string, { label: string; data: unknown }> = {};"
|
|
1843
|
+
);
|
|
1844
|
+
workflowBody.push("");
|
|
1845
|
+
for (const trigger of triggerNodes) {
|
|
1846
|
+
workflowBody.push(...generateWorkflowBody(trigger.id));
|
|
1847
|
+
}
|
|
1848
|
+
const lastNode = nodes.at(-1);
|
|
1849
|
+
if (lastNode) {
|
|
1850
|
+
const lastVarName = `result_${sanitizeVarName(lastNode.id)}`;
|
|
1851
|
+
workflowBody.push("");
|
|
1852
|
+
workflowBody.push(` return ${lastVarName};`);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
const functionName = sanitizeFunctionName(workflowName);
|
|
1856
|
+
const mainFunction = `export async function ${functionName}() {
|
|
1857
|
+
"use workflow";
|
|
1858
|
+
|
|
1859
|
+
// Input from workflow trigger - replace with your trigger data
|
|
1860
|
+
const input: Record<string, unknown> = {};
|
|
1861
|
+
|
|
1862
|
+
${workflowBody.join("\n")}
|
|
1863
|
+
}`;
|
|
1864
|
+
const code = `${Array.from(imports).join("\n")}
|
|
1865
|
+
|
|
1866
|
+
${stepFunctions.join("\n\n")}
|
|
1867
|
+
|
|
1868
|
+
${mainFunction}
|
|
1869
|
+
`;
|
|
1870
|
+
return code;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// src/server/api/workflows.ts
|
|
1874
|
+
async function handleExecuteWorkflow(request, workflowId) {
|
|
1875
|
+
try {
|
|
1876
|
+
const user = await resolveUser(request);
|
|
1877
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
1878
|
+
const workflow = await db.query.workflows.findFirst({
|
|
1879
|
+
where: eq4(workflows.id, workflowId)
|
|
1880
|
+
});
|
|
1881
|
+
if (!workflow) {
|
|
1882
|
+
return NextResponse4.json({ error: "Workflow not found" }, { status: 404 });
|
|
1883
|
+
}
|
|
1884
|
+
if (workflow.userId !== user.id) {
|
|
1885
|
+
return NextResponse4.json({ error: "Forbidden" }, { status: 403 });
|
|
1886
|
+
}
|
|
1887
|
+
const validation = await validateWorkflowIntegrations(
|
|
1888
|
+
workflow.nodes,
|
|
1889
|
+
user.id
|
|
1890
|
+
);
|
|
1891
|
+
if (!validation.valid) {
|
|
1892
|
+
return NextResponse4.json(
|
|
1893
|
+
{ error: "Workflow contains invalid integration references" },
|
|
1894
|
+
{ status: 403 }
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
const body = await request.json().catch(() => ({}));
|
|
1898
|
+
const input = body.input || {};
|
|
1899
|
+
const [execution] = await db.insert(workflowExecutions).values({
|
|
1900
|
+
workflowId,
|
|
1901
|
+
userId: user.id,
|
|
1902
|
+
status: "running",
|
|
1903
|
+
input
|
|
1904
|
+
}).returning();
|
|
1905
|
+
executeWorkflowBackground(
|
|
1906
|
+
execution.id,
|
|
1907
|
+
workflowId,
|
|
1908
|
+
workflow.nodes,
|
|
1909
|
+
workflow.edges,
|
|
1910
|
+
input
|
|
1911
|
+
);
|
|
1912
|
+
return NextResponse4.json({ executionId: execution.id, status: "running" });
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
console.error("Failed to start workflow execution:", error);
|
|
1915
|
+
return NextResponse4.json(
|
|
1916
|
+
{ error: error instanceof Error ? error.message : "Failed to execute workflow" },
|
|
1917
|
+
{ status: 500 }
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
async function handleGetWorkflow(request, workflowId) {
|
|
1922
|
+
try {
|
|
1923
|
+
const user = await resolveUser(request);
|
|
1924
|
+
const workflow = await db.query.workflows.findFirst({
|
|
1925
|
+
where: eq4(workflows.id, workflowId)
|
|
1926
|
+
});
|
|
1927
|
+
if (!workflow) {
|
|
1928
|
+
return NextResponse4.json({ error: "Workflow not found" }, { status: 404 });
|
|
1929
|
+
}
|
|
1930
|
+
const isOwner = user?.id === workflow.userId;
|
|
1931
|
+
if (!isOwner && workflow.visibility !== "public") {
|
|
1932
|
+
return NextResponse4.json({ error: "Workflow not found" }, { status: 404 });
|
|
1933
|
+
}
|
|
1934
|
+
return NextResponse4.json({
|
|
1935
|
+
...workflow,
|
|
1936
|
+
nodes: isOwner ? workflow.nodes : sanitizeNodesForPublicView(workflow.nodes),
|
|
1937
|
+
createdAt: workflow.createdAt.toISOString(),
|
|
1938
|
+
updatedAt: workflow.updatedAt.toISOString(),
|
|
1939
|
+
isOwner
|
|
1940
|
+
});
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
console.error("Failed to get workflow:", error);
|
|
1943
|
+
return NextResponse4.json(
|
|
1944
|
+
{ error: error instanceof Error ? error.message : "Failed to get workflow" },
|
|
1945
|
+
{ status: 500 }
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
async function handleGetWorkflows(request) {
|
|
1950
|
+
try {
|
|
1951
|
+
const user = await resolveUser(request);
|
|
1952
|
+
if (!user) return NextResponse4.json([], { status: 200 });
|
|
1953
|
+
const userWorkflows = await db.select().from(workflows).where(eq4(workflows.userId, user.id)).orderBy(desc(workflows.updatedAt));
|
|
1954
|
+
return NextResponse4.json(
|
|
1955
|
+
userWorkflows.map((w) => ({
|
|
1956
|
+
...w,
|
|
1957
|
+
createdAt: w.createdAt.toISOString(),
|
|
1958
|
+
updatedAt: w.updatedAt.toISOString()
|
|
1959
|
+
}))
|
|
1960
|
+
);
|
|
1961
|
+
} catch (error) {
|
|
1962
|
+
console.error("Failed to get workflows:", error);
|
|
1963
|
+
return NextResponse4.json(
|
|
1964
|
+
{ error: error instanceof Error ? error.message : "Failed to get workflows" },
|
|
1965
|
+
{ status: 500 }
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
async function handleCreateWorkflow(request) {
|
|
1970
|
+
try {
|
|
1971
|
+
const user = await resolveUser(request);
|
|
1972
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
1973
|
+
const body = await request.json();
|
|
1974
|
+
if (!(body.name && body.nodes && body.edges)) {
|
|
1975
|
+
return NextResponse4.json(
|
|
1976
|
+
{ error: "Name, nodes, and edges are required" },
|
|
1977
|
+
{ status: 400 }
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
const validation = await validateWorkflowIntegrations(body.nodes, user.id);
|
|
1981
|
+
if (!validation.valid) {
|
|
1982
|
+
return NextResponse4.json(
|
|
1983
|
+
{ error: "Invalid integration references in workflow" },
|
|
1984
|
+
{ status: 403 }
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
let nodes = body.nodes;
|
|
1988
|
+
if (nodes.length === 0) {
|
|
1989
|
+
nodes = [
|
|
1990
|
+
{
|
|
1991
|
+
id: generateId(),
|
|
1992
|
+
type: "trigger",
|
|
1993
|
+
position: { x: 0, y: 0 },
|
|
1994
|
+
data: {
|
|
1995
|
+
label: "",
|
|
1996
|
+
description: "",
|
|
1997
|
+
type: "trigger",
|
|
1998
|
+
config: { triggerType: "Manual" },
|
|
1999
|
+
status: "idle"
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
];
|
|
2003
|
+
}
|
|
2004
|
+
let workflowName = body.name;
|
|
2005
|
+
if (body.name === "Untitled Workflow") {
|
|
2006
|
+
const userWorkflows = await db.query.workflows.findMany({
|
|
2007
|
+
where: eq4(workflows.userId, user.id)
|
|
2008
|
+
});
|
|
2009
|
+
workflowName = `Untitled ${userWorkflows.length + 1}`;
|
|
2010
|
+
}
|
|
2011
|
+
const workflowId = generateId();
|
|
2012
|
+
const [newWorkflow] = await db.insert(workflows).values({
|
|
2013
|
+
id: workflowId,
|
|
2014
|
+
name: workflowName,
|
|
2015
|
+
description: body.description,
|
|
2016
|
+
nodes,
|
|
2017
|
+
edges: body.edges,
|
|
2018
|
+
userId: user.id
|
|
2019
|
+
}).returning();
|
|
2020
|
+
return NextResponse4.json({
|
|
2021
|
+
...newWorkflow,
|
|
2022
|
+
createdAt: newWorkflow.createdAt.toISOString(),
|
|
2023
|
+
updatedAt: newWorkflow.updatedAt.toISOString()
|
|
2024
|
+
});
|
|
2025
|
+
} catch (error) {
|
|
2026
|
+
console.error("Failed to create workflow:", error);
|
|
2027
|
+
return NextResponse4.json(
|
|
2028
|
+
{ error: error instanceof Error ? error.message : "Failed to create workflow" },
|
|
2029
|
+
{ status: 500 }
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
var CURRENT_WORKFLOW_NAME = "~~__CURRENT__~~";
|
|
2034
|
+
async function handleGetCurrentWorkflow(request) {
|
|
2035
|
+
try {
|
|
2036
|
+
const user = await resolveUser(request);
|
|
2037
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2038
|
+
const [currentWorkflow] = await db.select().from(workflows).where(and2(eq4(workflows.name, CURRENT_WORKFLOW_NAME), eq4(workflows.userId, user.id))).orderBy(desc(workflows.updatedAt)).limit(1);
|
|
2039
|
+
if (!currentWorkflow) {
|
|
2040
|
+
return NextResponse4.json({ nodes: [], edges: [] });
|
|
2041
|
+
}
|
|
2042
|
+
return NextResponse4.json({
|
|
2043
|
+
id: currentWorkflow.id,
|
|
2044
|
+
nodes: currentWorkflow.nodes,
|
|
2045
|
+
edges: currentWorkflow.edges
|
|
2046
|
+
});
|
|
2047
|
+
} catch (error) {
|
|
2048
|
+
console.error("Failed to get current workflow:", error);
|
|
2049
|
+
return NextResponse4.json(
|
|
2050
|
+
{ error: error instanceof Error ? error.message : "Failed to get current workflow" },
|
|
2051
|
+
{ status: 500 }
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
async function handleSaveCurrentWorkflow(request) {
|
|
2056
|
+
try {
|
|
2057
|
+
const user = await resolveUser(request);
|
|
2058
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2059
|
+
const body = await request.json();
|
|
2060
|
+
const { nodes, edges } = body;
|
|
2061
|
+
if (!(nodes && edges)) {
|
|
2062
|
+
return NextResponse4.json({ error: "Nodes and edges are required" }, { status: 400 });
|
|
2063
|
+
}
|
|
2064
|
+
const [existing] = await db.select().from(workflows).where(and2(eq4(workflows.name, CURRENT_WORKFLOW_NAME), eq4(workflows.userId, user.id))).limit(1);
|
|
2065
|
+
if (existing) {
|
|
2066
|
+
const [updated] = await db.update(workflows).set({ nodes, edges, updatedAt: /* @__PURE__ */ new Date() }).where(eq4(workflows.id, existing.id)).returning();
|
|
2067
|
+
return NextResponse4.json({ id: updated.id, nodes: updated.nodes, edges: updated.edges });
|
|
2068
|
+
}
|
|
2069
|
+
const workflowId = generateId();
|
|
2070
|
+
const [saved] = await db.insert(workflows).values({
|
|
2071
|
+
id: workflowId,
|
|
2072
|
+
name: CURRENT_WORKFLOW_NAME,
|
|
2073
|
+
description: "Auto-saved current workflow",
|
|
2074
|
+
nodes,
|
|
2075
|
+
edges,
|
|
2076
|
+
userId: user.id
|
|
2077
|
+
}).returning();
|
|
2078
|
+
return NextResponse4.json({ id: saved.id, nodes: saved.nodes, edges: saved.edges });
|
|
2079
|
+
} catch (error) {
|
|
2080
|
+
console.error("Failed to save current workflow:", error);
|
|
2081
|
+
return NextResponse4.json(
|
|
2082
|
+
{ error: error instanceof Error ? error.message : "Failed to save current workflow" },
|
|
2083
|
+
{ status: 500 }
|
|
2084
|
+
);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
async function handleGetExecutionStatus(request, executionId) {
|
|
2088
|
+
try {
|
|
2089
|
+
const user = await resolveUser(request);
|
|
2090
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2091
|
+
const execution = await db.query.workflowExecutions.findFirst({
|
|
2092
|
+
where: eq4(workflowExecutions.id, executionId),
|
|
2093
|
+
with: { workflow: true }
|
|
2094
|
+
});
|
|
2095
|
+
if (!execution) {
|
|
2096
|
+
return NextResponse4.json({ error: "Execution not found" }, { status: 404 });
|
|
2097
|
+
}
|
|
2098
|
+
if (execution.workflow.userId !== user.id) {
|
|
2099
|
+
return NextResponse4.json({ error: "Forbidden" }, { status: 403 });
|
|
2100
|
+
}
|
|
2101
|
+
const logs = await db.query.workflowExecutionLogs.findMany({
|
|
2102
|
+
where: eq4(workflowExecutionLogs.executionId, executionId)
|
|
2103
|
+
});
|
|
2104
|
+
const nodeStatuses = logs.map((log) => ({
|
|
2105
|
+
nodeId: log.nodeId,
|
|
2106
|
+
status: log.status
|
|
2107
|
+
}));
|
|
2108
|
+
return NextResponse4.json({ status: execution.status, nodeStatuses });
|
|
2109
|
+
} catch (error) {
|
|
2110
|
+
console.error("Failed to get execution status:", error);
|
|
2111
|
+
return NextResponse4.json(
|
|
2112
|
+
{ error: error instanceof Error ? error.message : "Failed to get execution status" },
|
|
2113
|
+
{ status: 500 }
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
async function handleGetExecutionLogs(request, executionId) {
|
|
2118
|
+
try {
|
|
2119
|
+
const user = await resolveUser(request);
|
|
2120
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2121
|
+
const execution = await db.query.workflowExecutions.findFirst({
|
|
2122
|
+
where: eq4(workflowExecutions.id, executionId),
|
|
2123
|
+
with: { workflow: true }
|
|
2124
|
+
});
|
|
2125
|
+
if (!execution) {
|
|
2126
|
+
return NextResponse4.json({ error: "Execution not found" }, { status: 404 });
|
|
2127
|
+
}
|
|
2128
|
+
if (execution.workflow.userId !== user.id) {
|
|
2129
|
+
return NextResponse4.json({ error: "Forbidden" }, { status: 403 });
|
|
2130
|
+
}
|
|
2131
|
+
const logs = await db.query.workflowExecutionLogs.findMany({
|
|
2132
|
+
where: eq4(workflowExecutionLogs.executionId, executionId),
|
|
2133
|
+
orderBy: [desc(workflowExecutionLogs.timestamp)]
|
|
2134
|
+
});
|
|
2135
|
+
return NextResponse4.json({ execution, logs });
|
|
2136
|
+
} catch (error) {
|
|
2137
|
+
console.error("Failed to get execution logs:", error);
|
|
2138
|
+
return NextResponse4.json(
|
|
2139
|
+
{ error: error instanceof Error ? error.message : "Failed to get execution logs" },
|
|
2140
|
+
{ status: 500 }
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
async function handlePatchWorkflow(request, workflowId) {
|
|
2145
|
+
try {
|
|
2146
|
+
const user = await resolveUser(request);
|
|
2147
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2148
|
+
const existingWorkflow = await db.query.workflows.findFirst({
|
|
2149
|
+
where: and2(eq4(workflows.id, workflowId), eq4(workflows.userId, user.id))
|
|
2150
|
+
});
|
|
2151
|
+
if (!existingWorkflow) {
|
|
2152
|
+
return NextResponse4.json({ error: "Workflow not found" }, { status: 404 });
|
|
2153
|
+
}
|
|
2154
|
+
const body = await request.json();
|
|
2155
|
+
if (Array.isArray(body.nodes)) {
|
|
2156
|
+
const validation = await validateWorkflowIntegrations(body.nodes, user.id);
|
|
2157
|
+
if (!validation.valid) {
|
|
2158
|
+
return NextResponse4.json(
|
|
2159
|
+
{ error: "Invalid integration references in workflow" },
|
|
2160
|
+
{ status: 403 }
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
if (body.visibility !== void 0 && body.visibility !== "private" && body.visibility !== "public") {
|
|
2165
|
+
return NextResponse4.json(
|
|
2166
|
+
{ error: "Invalid visibility value. Must be 'private' or 'public'" },
|
|
2167
|
+
{ status: 400 }
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
const updateData = buildWorkflowUpdateData(body);
|
|
2171
|
+
const [updatedWorkflow] = await db.update(workflows).set(updateData).where(eq4(workflows.id, workflowId)).returning();
|
|
2172
|
+
if (!updatedWorkflow) {
|
|
2173
|
+
return NextResponse4.json({ error: "Workflow not found" }, { status: 404 });
|
|
2174
|
+
}
|
|
2175
|
+
return NextResponse4.json({
|
|
2176
|
+
...updatedWorkflow,
|
|
2177
|
+
createdAt: updatedWorkflow.createdAt.toISOString(),
|
|
2178
|
+
updatedAt: updatedWorkflow.updatedAt.toISOString(),
|
|
2179
|
+
isOwner: true
|
|
2180
|
+
});
|
|
2181
|
+
} catch (error) {
|
|
2182
|
+
console.error("Failed to update workflow:", error);
|
|
2183
|
+
return NextResponse4.json(
|
|
2184
|
+
{ error: error instanceof Error ? error.message : "Failed to update workflow" },
|
|
2185
|
+
{ status: 500 }
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
async function handleDeleteWorkflow(request, workflowId) {
|
|
2190
|
+
try {
|
|
2191
|
+
const user = await resolveUser(request);
|
|
2192
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2193
|
+
const existingWorkflow = await db.query.workflows.findFirst({
|
|
2194
|
+
where: and2(eq4(workflows.id, workflowId), eq4(workflows.userId, user.id))
|
|
2195
|
+
});
|
|
2196
|
+
if (!existingWorkflow) {
|
|
2197
|
+
return NextResponse4.json({ error: "Workflow not found" }, { status: 404 });
|
|
2198
|
+
}
|
|
2199
|
+
await db.delete(workflows).where(eq4(workflows.id, workflowId));
|
|
2200
|
+
return NextResponse4.json({ success: true });
|
|
2201
|
+
} catch (error) {
|
|
2202
|
+
console.error("Failed to delete workflow:", error);
|
|
2203
|
+
return NextResponse4.json(
|
|
2204
|
+
{ error: error instanceof Error ? error.message : "Failed to delete workflow" },
|
|
2205
|
+
{ status: 500 }
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
async function handleDuplicateWorkflow(request, workflowId) {
|
|
2210
|
+
try {
|
|
2211
|
+
const user = await resolveUser(request);
|
|
2212
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2213
|
+
const sourceWorkflow = await db.query.workflows.findFirst({
|
|
2214
|
+
where: eq4(workflows.id, workflowId)
|
|
2215
|
+
});
|
|
2216
|
+
if (!sourceWorkflow) {
|
|
2217
|
+
return NextResponse4.json({ error: "Workflow not found" }, { status: 404 });
|
|
2218
|
+
}
|
|
2219
|
+
const isOwner = user.id === sourceWorkflow.userId;
|
|
2220
|
+
if (!isOwner && sourceWorkflow.visibility !== "public") {
|
|
2221
|
+
return NextResponse4.json({ error: "Workflow not found" }, { status: 404 });
|
|
2222
|
+
}
|
|
2223
|
+
const oldNodes = sourceWorkflow.nodes;
|
|
2224
|
+
const newNodes = oldNodes.map((node) => {
|
|
2225
|
+
const newNode = { ...node, id: generateId() };
|
|
2226
|
+
if (newNode.data) {
|
|
2227
|
+
const data = { ...newNode.data };
|
|
2228
|
+
if (data.config) {
|
|
2229
|
+
const { integrationId: _, ...configWithout } = data.config;
|
|
2230
|
+
data.config = configWithout;
|
|
2231
|
+
}
|
|
2232
|
+
data.status = "idle";
|
|
2233
|
+
newNode.data = data;
|
|
2234
|
+
}
|
|
2235
|
+
return newNode;
|
|
2236
|
+
});
|
|
2237
|
+
const idMap = new Map(oldNodes.map((n, i) => [n.id, newNodes[i].id]));
|
|
2238
|
+
const newEdges = sourceWorkflow.edges.map((edge) => ({
|
|
2239
|
+
...edge,
|
|
2240
|
+
id: generateId(),
|
|
2241
|
+
source: idMap.get(edge.source) || edge.source,
|
|
2242
|
+
target: idMap.get(edge.target) || edge.target
|
|
2243
|
+
}));
|
|
2244
|
+
const userWorkflows = await db.query.workflows.findMany({
|
|
2245
|
+
where: eq4(workflows.userId, user.id)
|
|
2246
|
+
});
|
|
2247
|
+
const baseName = `${sourceWorkflow.name} (Copy)`;
|
|
2248
|
+
let workflowName = baseName;
|
|
2249
|
+
const existingNames = new Set(userWorkflows.map((w) => w.name));
|
|
2250
|
+
if (existingNames.has(workflowName)) {
|
|
2251
|
+
let counter = 2;
|
|
2252
|
+
while (existingNames.has(`${baseName} ${counter}`)) counter++;
|
|
2253
|
+
workflowName = `${baseName} ${counter}`;
|
|
2254
|
+
}
|
|
2255
|
+
const newWorkflowId = generateId();
|
|
2256
|
+
const [newWorkflow] = await db.insert(workflows).values({
|
|
2257
|
+
id: newWorkflowId,
|
|
2258
|
+
name: workflowName,
|
|
2259
|
+
description: sourceWorkflow.description,
|
|
2260
|
+
nodes: newNodes,
|
|
2261
|
+
edges: newEdges,
|
|
2262
|
+
userId: user.id,
|
|
2263
|
+
visibility: "private"
|
|
2264
|
+
}).returning();
|
|
2265
|
+
return NextResponse4.json({
|
|
2266
|
+
...newWorkflow,
|
|
2267
|
+
createdAt: newWorkflow.createdAt.toISOString(),
|
|
2268
|
+
updatedAt: newWorkflow.updatedAt.toISOString(),
|
|
2269
|
+
isOwner: true
|
|
2270
|
+
});
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
console.error("Failed to duplicate workflow:", error);
|
|
2273
|
+
return NextResponse4.json(
|
|
2274
|
+
{ error: error instanceof Error ? error.message : "Failed to duplicate workflow" },
|
|
2275
|
+
{ status: 500 }
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
async function handleGetWorkflowExecutions(request, workflowId) {
|
|
2280
|
+
try {
|
|
2281
|
+
const user = await resolveUser(request);
|
|
2282
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2283
|
+
const workflow = await db.query.workflows.findFirst({
|
|
2284
|
+
where: and2(eq4(workflows.id, workflowId), eq4(workflows.userId, user.id))
|
|
2285
|
+
});
|
|
2286
|
+
if (!workflow) {
|
|
2287
|
+
return NextResponse4.json({ error: "Workflow not found" }, { status: 404 });
|
|
2288
|
+
}
|
|
2289
|
+
const executions = await db.query.workflowExecutions.findMany({
|
|
2290
|
+
where: eq4(workflowExecutions.workflowId, workflowId),
|
|
2291
|
+
orderBy: [desc(workflowExecutions.startedAt)],
|
|
2292
|
+
limit: 50
|
|
2293
|
+
});
|
|
2294
|
+
return NextResponse4.json(executions);
|
|
2295
|
+
} catch (error) {
|
|
2296
|
+
console.error("Failed to get executions:", error);
|
|
2297
|
+
return NextResponse4.json(
|
|
2298
|
+
{ error: error instanceof Error ? error.message : "Failed to get executions" },
|
|
2299
|
+
{ status: 500 }
|
|
2300
|
+
);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
async function handleDeleteWorkflowExecutions(request, workflowId) {
|
|
2304
|
+
try {
|
|
2305
|
+
const user = await resolveUser(request);
|
|
2306
|
+
if (!user) return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2307
|
+
const workflow = await db.query.workflows.findFirst({
|
|
2308
|
+
where: and2(eq4(workflows.id, workflowId), eq4(workflows.userId, user.id))
|
|
2309
|
+
});
|
|
2310
|
+
if (!workflow) {
|
|
2311
|
+
return NextResponse4.json({ error: "Workflow not found" }, { status: 404 });
|
|
2312
|
+
}
|
|
2313
|
+
const execList = await db.query.workflowExecutions.findMany({
|
|
2314
|
+
where: eq4(workflowExecutions.workflowId, workflowId),
|
|
2315
|
+
columns: { id: true }
|
|
2316
|
+
});
|
|
2317
|
+
const executionIds = execList.map((e) => e.id);
|
|
2318
|
+
if (executionIds.length > 0) {
|
|
2319
|
+
await db.delete(workflowExecutionLogs).where(inArray(workflowExecutionLogs.executionId, executionIds));
|
|
2320
|
+
await db.delete(workflowExecutions).where(eq4(workflowExecutions.workflowId, workflowId));
|
|
2321
|
+
}
|
|
2322
|
+
return NextResponse4.json({ success: true, deletedCount: executionIds.length });
|
|
2323
|
+
} catch (error) {
|
|
2324
|
+
console.error("Failed to delete executions:", error);
|
|
2325
|
+
return NextResponse4.json(
|
|
2326
|
+
{ error: error instanceof Error ? error.message : "Failed to delete executions" },
|
|
2327
|
+
{ status: 500 }
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
async function handleWebhookWorkflow(request, workflowId) {
|
|
2332
|
+
try {
|
|
2333
|
+
const workflow = await db.query.workflows.findFirst({
|
|
2334
|
+
where: eq4(workflows.id, workflowId)
|
|
2335
|
+
});
|
|
2336
|
+
if (!workflow) {
|
|
2337
|
+
return NextResponse4.json(
|
|
2338
|
+
{ error: "Workflow not found" },
|
|
2339
|
+
{ status: 404, headers: corsHeaders }
|
|
2340
|
+
);
|
|
2341
|
+
}
|
|
2342
|
+
const authHeader = request.headers.get("Authorization");
|
|
2343
|
+
if (!authHeader) {
|
|
2344
|
+
return NextResponse4.json(
|
|
2345
|
+
{ error: "Missing Authorization header" },
|
|
2346
|
+
{ status: 401, headers: corsHeaders }
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
const key = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
|
|
2350
|
+
if (!key?.startsWith("wfb_")) {
|
|
2351
|
+
return NextResponse4.json(
|
|
2352
|
+
{ error: "Invalid API key format" },
|
|
2353
|
+
{ status: 401, headers: corsHeaders }
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
const keyHash = createHash2("sha256").update(key).digest("hex");
|
|
2357
|
+
const apiKey = await db.query.apiKeys.findFirst({
|
|
2358
|
+
where: eq4(apiKeys.keyHash, keyHash)
|
|
2359
|
+
});
|
|
2360
|
+
if (!apiKey || apiKey.userId !== workflow.userId) {
|
|
2361
|
+
return NextResponse4.json(
|
|
2362
|
+
{ error: "Invalid API key or insufficient permissions" },
|
|
2363
|
+
{ status: 401, headers: corsHeaders }
|
|
2364
|
+
);
|
|
2365
|
+
}
|
|
2366
|
+
db.update(apiKeys).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(eq4(apiKeys.id, apiKey.id)).catch(() => {
|
|
2367
|
+
});
|
|
2368
|
+
const triggerNode = workflow.nodes.find(
|
|
2369
|
+
(node) => node.data.type === "trigger"
|
|
2370
|
+
);
|
|
2371
|
+
if (!triggerNode || triggerNode.data.config?.triggerType !== "Webhook") {
|
|
2372
|
+
return NextResponse4.json(
|
|
2373
|
+
{ error: "This workflow is not configured for webhook triggers" },
|
|
2374
|
+
{ status: 400, headers: corsHeaders }
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
const validation = await validateWorkflowIntegrations(
|
|
2378
|
+
workflow.nodes,
|
|
2379
|
+
workflow.userId
|
|
2380
|
+
);
|
|
2381
|
+
if (!validation.valid) {
|
|
2382
|
+
return NextResponse4.json(
|
|
2383
|
+
{ error: "Workflow contains invalid integration references" },
|
|
2384
|
+
{ status: 403, headers: corsHeaders }
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
const body = await request.json().catch(() => ({}));
|
|
2388
|
+
const [execution] = await db.insert(workflowExecutions).values({
|
|
2389
|
+
workflowId,
|
|
2390
|
+
userId: workflow.userId,
|
|
2391
|
+
status: "running",
|
|
2392
|
+
input: body
|
|
2393
|
+
}).returning();
|
|
2394
|
+
executeWorkflowBackground(
|
|
2395
|
+
execution.id,
|
|
2396
|
+
workflowId,
|
|
2397
|
+
workflow.nodes,
|
|
2398
|
+
workflow.edges,
|
|
2399
|
+
body
|
|
2400
|
+
);
|
|
2401
|
+
return NextResponse4.json(
|
|
2402
|
+
{ executionId: execution.id, status: "running" },
|
|
2403
|
+
{ headers: corsHeaders }
|
|
2404
|
+
);
|
|
2405
|
+
} catch (error) {
|
|
2406
|
+
console.error("Failed to start webhook execution:", error);
|
|
2407
|
+
return NextResponse4.json(
|
|
2408
|
+
{ error: error instanceof Error ? error.message : "Failed to execute workflow" },
|
|
2409
|
+
{ status: 500, headers: corsHeaders }
|
|
2410
|
+
);
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
async function handleGetWorkflowCode(request, workflowId) {
|
|
2414
|
+
try {
|
|
2415
|
+
const session = await auth.api.getSession({
|
|
2416
|
+
headers: request.headers
|
|
2417
|
+
});
|
|
2418
|
+
if (!session?.user) {
|
|
2419
|
+
return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2420
|
+
}
|
|
2421
|
+
const workflow = await db.query.workflows.findFirst({
|
|
2422
|
+
where: and2(
|
|
2423
|
+
eq4(workflows.id, workflowId),
|
|
2424
|
+
eq4(workflows.userId, session.user.id)
|
|
2425
|
+
)
|
|
2426
|
+
});
|
|
2427
|
+
if (!workflow) {
|
|
2428
|
+
return NextResponse4.json(
|
|
2429
|
+
{ error: "Workflow not found" },
|
|
2430
|
+
{ status: 404 }
|
|
2431
|
+
);
|
|
2432
|
+
}
|
|
2433
|
+
const code = generateWorkflowSDKCode(
|
|
2434
|
+
workflow.name,
|
|
2435
|
+
workflow.nodes,
|
|
2436
|
+
workflow.edges
|
|
2437
|
+
);
|
|
2438
|
+
return NextResponse4.json({
|
|
2439
|
+
code,
|
|
2440
|
+
workflowName: workflow.name
|
|
2441
|
+
});
|
|
2442
|
+
} catch (error) {
|
|
2443
|
+
console.error("Failed to get workflow code:", error);
|
|
2444
|
+
return NextResponse4.json(
|
|
2445
|
+
{
|
|
2446
|
+
error: error instanceof Error ? error.message : "Failed to get workflow code"
|
|
2447
|
+
},
|
|
2448
|
+
{ status: 500 }
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
async function handleGetWorkflowDownload(request, workflowId) {
|
|
2453
|
+
try {
|
|
2454
|
+
const session = await auth.api.getSession({
|
|
2455
|
+
headers: request.headers
|
|
2456
|
+
});
|
|
2457
|
+
if (!session?.user) {
|
|
2458
|
+
return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
|
|
2459
|
+
}
|
|
2460
|
+
const workflow = await db.query.workflows.findFirst({
|
|
2461
|
+
where: and2(
|
|
2462
|
+
eq4(workflows.id, workflowId),
|
|
2463
|
+
eq4(workflows.userId, session.user.id)
|
|
2464
|
+
)
|
|
2465
|
+
});
|
|
2466
|
+
if (!workflow) {
|
|
2467
|
+
return NextResponse4.json(
|
|
2468
|
+
{ error: "Workflow not found" },
|
|
2469
|
+
{ status: 404 }
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
const boilerplateFiles = await readDirectoryRecursive(BOILERPLATE_PATH);
|
|
2473
|
+
const templateFiles = await readDirectoryRecursive(CODEGEN_TEMPLATES_PATH);
|
|
2474
|
+
const stepFiles = {};
|
|
2475
|
+
for (const [path, content] of Object.entries(templateFiles)) {
|
|
2476
|
+
const templateMatch = content.match(TEMPLATE_EXPORT_REGEX);
|
|
2477
|
+
if (templateMatch) {
|
|
2478
|
+
stepFiles[`lib/steps/${path}`] = templateMatch[1];
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
const workflowFiles = generateWorkflowFiles({
|
|
2482
|
+
name: workflow.name,
|
|
2483
|
+
nodes: workflow.nodes,
|
|
2484
|
+
edges: workflow.edges
|
|
2485
|
+
});
|
|
2486
|
+
const allFiles = { ...boilerplateFiles, ...stepFiles, ...workflowFiles };
|
|
2487
|
+
const packageJson = JSON.parse(allFiles["package.json"]);
|
|
2488
|
+
packageJson.dependencies = {
|
|
2489
|
+
...packageJson.dependencies,
|
|
2490
|
+
workflow: "4.0.1-beta.7",
|
|
2491
|
+
...getIntegrationDependencies(workflow.nodes)
|
|
2492
|
+
};
|
|
2493
|
+
allFiles["package.json"] = JSON.stringify(packageJson, null, 2);
|
|
2494
|
+
allFiles["next.config.ts"] = `import { withWorkflow } from 'workflow/next';
|
|
2495
|
+
import type { NextConfig } from 'next';
|
|
2496
|
+
|
|
2497
|
+
const nextConfig: NextConfig = {};
|
|
2498
|
+
|
|
2499
|
+
export default withWorkflow(nextConfig);
|
|
2500
|
+
`;
|
|
2501
|
+
const tsConfig = JSON.parse(allFiles["tsconfig.json"]);
|
|
2502
|
+
tsConfig.compilerOptions.plugins = [{ name: "next" }, { name: "workflow" }];
|
|
2503
|
+
allFiles["tsconfig.json"] = JSON.stringify(tsConfig, null, 2);
|
|
2504
|
+
allFiles["README.md"] = `# ${workflow.name}
|
|
2505
|
+
|
|
2506
|
+
This is a Next.js workflow project generated from Workflow Builder.
|
|
2507
|
+
|
|
2508
|
+
## Getting Started
|
|
2509
|
+
|
|
2510
|
+
1. Install dependencies:
|
|
2511
|
+
\`\`\`bash
|
|
2512
|
+
pnpm install
|
|
2513
|
+
\`\`\`
|
|
2514
|
+
|
|
2515
|
+
2. Set up environment variables:
|
|
2516
|
+
\`\`\`bash
|
|
2517
|
+
cp .env.example .env.local
|
|
2518
|
+
\`\`\`
|
|
2519
|
+
|
|
2520
|
+
3. Run the development server:
|
|
2521
|
+
\`\`\`bash
|
|
2522
|
+
pnpm dev
|
|
2523
|
+
\`\`\`
|
|
2524
|
+
|
|
2525
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
2526
|
+
|
|
2527
|
+
## Workflow API
|
|
2528
|
+
|
|
2529
|
+
Your workflow is available at \`/api/workflows/${sanitizeFileName(workflow.name)}\`.
|
|
2530
|
+
|
|
2531
|
+
Send a POST request with a JSON body to trigger the workflow:
|
|
2532
|
+
|
|
2533
|
+
\`\`\`bash
|
|
2534
|
+
curl -X POST http://localhost:3000/api/workflows/${sanitizeFileName(workflow.name)} \\
|
|
2535
|
+
-H "Content-Type: application/json" \\
|
|
2536
|
+
-d '{"key": "value"}'
|
|
2537
|
+
\`\`\`
|
|
2538
|
+
|
|
2539
|
+
## Deployment
|
|
2540
|
+
|
|
2541
|
+
Deploy your workflow to Vercel:
|
|
2542
|
+
|
|
2543
|
+
\`\`\`bash
|
|
2544
|
+
vercel deploy
|
|
2545
|
+
\`\`\`
|
|
2546
|
+
|
|
2547
|
+
For more information, visit the [Workflow documentation](https://workflow.is).
|
|
2548
|
+
`;
|
|
2549
|
+
allFiles[".env.example"] = generateEnvExample();
|
|
2550
|
+
return NextResponse4.json({
|
|
2551
|
+
success: true,
|
|
2552
|
+
files: allFiles
|
|
2553
|
+
});
|
|
2554
|
+
} catch (error) {
|
|
2555
|
+
console.error("Failed to prepare workflow download:", error);
|
|
2556
|
+
return NextResponse4.json(
|
|
2557
|
+
{
|
|
2558
|
+
success: false,
|
|
2559
|
+
error: error instanceof Error ? error.message : "Failed to prepare workflow download"
|
|
2560
|
+
},
|
|
2561
|
+
{ status: 500 }
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
// src/server/api/index.ts
|
|
2567
|
+
import "virtual:workflow-builder-plugins";
|
|
2568
|
+
async function route(request) {
|
|
2569
|
+
const segments = extractPath(request);
|
|
2570
|
+
const method = request.method.toUpperCase();
|
|
2571
|
+
if (segments.length === 0) {
|
|
2572
|
+
return NextResponse5.json({ error: "Not found" }, { status: 404 });
|
|
2573
|
+
}
|
|
2574
|
+
const [s0, s1, s2, s3] = segments;
|
|
2575
|
+
if (s0 === "auth") {
|
|
2576
|
+
return handleAuth(request, segments);
|
|
2577
|
+
}
|
|
2578
|
+
if (s0 === "workflows") {
|
|
2579
|
+
if (s1 === void 0) {
|
|
2580
|
+
if (method === "GET") return handleGetWorkflows(request);
|
|
2581
|
+
}
|
|
2582
|
+
if (s1 === "create") {
|
|
2583
|
+
if (method === "POST") return handleCreateWorkflow(request);
|
|
2584
|
+
}
|
|
2585
|
+
if (s1 === "current") {
|
|
2586
|
+
if (method === "GET") return handleGetCurrentWorkflow(request);
|
|
2587
|
+
if (method === "POST") return handleSaveCurrentWorkflow(request);
|
|
2588
|
+
}
|
|
2589
|
+
if (s1 === "executions" && s2) {
|
|
2590
|
+
const executionId = s2;
|
|
2591
|
+
if (s3 === "status" && method === "GET") return handleGetExecutionStatus(request, executionId);
|
|
2592
|
+
if (s3 === "logs" && method === "GET") return handleGetExecutionLogs(request, executionId);
|
|
2593
|
+
}
|
|
2594
|
+
if (s1 && s1 !== "create" && s1 !== "current" && s1 !== "executions") {
|
|
2595
|
+
const workflowId = s1;
|
|
2596
|
+
if (s2 === void 0) {
|
|
2597
|
+
if (method === "GET") return handleGetWorkflow(request, workflowId);
|
|
2598
|
+
if (method === "PATCH") return handlePatchWorkflow(request, workflowId);
|
|
2599
|
+
if (method === "DELETE") return handleDeleteWorkflow(request, workflowId);
|
|
2600
|
+
}
|
|
2601
|
+
if (s2 === "code" && method === "GET") return handleGetWorkflowCode(request, workflowId);
|
|
2602
|
+
if (s2 === "download" && method === "GET") return handleGetWorkflowDownload(request, workflowId);
|
|
2603
|
+
if (s2 === "duplicate" && method === "POST") return handleDuplicateWorkflow(request, workflowId);
|
|
2604
|
+
if (s2 === "executions") {
|
|
2605
|
+
if (method === "GET") return handleGetWorkflowExecutions(request, workflowId);
|
|
2606
|
+
if (method === "DELETE") return handleDeleteWorkflowExecutions(request, workflowId);
|
|
2607
|
+
}
|
|
2608
|
+
if (s2 === "webhook" && method === "POST") return handleWebhookWorkflow(request, workflowId);
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
if (s0 === "workflow" && s1 && s2 === "execute") {
|
|
2612
|
+
if (method === "POST") return handleExecuteWorkflow(request, s1);
|
|
2613
|
+
}
|
|
2614
|
+
if (s0 === "integrations") {
|
|
2615
|
+
if (s1 === void 0) {
|
|
2616
|
+
if (method === "GET") return handleGetIntegrations(request);
|
|
2617
|
+
if (method === "POST") return handleCreateIntegration(request);
|
|
2618
|
+
}
|
|
2619
|
+
if (s1 === "test" && s2 === void 0) {
|
|
2620
|
+
if (method === "POST") return handleTestIntegrationCredentials(request);
|
|
2621
|
+
}
|
|
2622
|
+
if (s1 && s1 !== "test") {
|
|
2623
|
+
const integrationId = s1;
|
|
2624
|
+
if (s2 === void 0) {
|
|
2625
|
+
if (method === "GET") return handleGetIntegration(request, integrationId);
|
|
2626
|
+
if (method === "PUT") return handleUpdateIntegration(request, integrationId);
|
|
2627
|
+
if (method === "DELETE") return handleDeleteIntegration(request, integrationId);
|
|
2628
|
+
}
|
|
2629
|
+
if (s2 === "test" && method === "POST") return handleTestIntegration(request, integrationId);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
if (s0 === "user") {
|
|
2633
|
+
if (method === "GET") return handleGetUser(request);
|
|
2634
|
+
if (method === "PATCH") return handleUpdateUser(request);
|
|
2635
|
+
}
|
|
2636
|
+
if (s0 === "api-keys") {
|
|
2637
|
+
if (s1 === void 0) {
|
|
2638
|
+
if (method === "GET") return handleGetApiKeys(request);
|
|
2639
|
+
if (method === "POST") return handleCreateApiKey(request);
|
|
2640
|
+
}
|
|
2641
|
+
if (s1 && s2 === void 0) {
|
|
2642
|
+
if (method === "DELETE") return handleDeleteApiKey(request, s1);
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
return NextResponse5.json({ error: "Not found" }, { status: 404 });
|
|
2646
|
+
}
|
|
2647
|
+
async function GET(request) {
|
|
2648
|
+
return route(request);
|
|
2649
|
+
}
|
|
2650
|
+
async function POST(request) {
|
|
2651
|
+
return route(request);
|
|
2652
|
+
}
|
|
2653
|
+
async function PUT(request) {
|
|
2654
|
+
return route(request);
|
|
2655
|
+
}
|
|
2656
|
+
async function PATCH(request) {
|
|
2657
|
+
return route(request);
|
|
2658
|
+
}
|
|
2659
|
+
async function DELETE(request) {
|
|
2660
|
+
return route(request);
|
|
2661
|
+
}
|
|
2662
|
+
async function OPTIONS(_request) {
|
|
2663
|
+
return NextResponse5.json({}, { headers: corsHeaders });
|
|
2664
|
+
}
|
|
2665
|
+
export {
|
|
2666
|
+
DELETE,
|
|
2667
|
+
GET,
|
|
2668
|
+
OPTIONS,
|
|
2669
|
+
PATCH,
|
|
2670
|
+
POST,
|
|
2671
|
+
PUT
|
|
2672
|
+
};
|