@timo9378/flow2code 0.1.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.
@@ -0,0 +1,4177 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/lib/index.ts
21
+ var lib_exports = {};
22
+ __export(lib_exports, {
23
+ ActionType: () => ActionType,
24
+ CURRENT_IR_VERSION: () => CURRENT_IR_VERSION,
25
+ LogicType: () => LogicType,
26
+ NodeCategory: () => NodeCategory,
27
+ NodeRegistry: () => NodeRegistry,
28
+ OutputType: () => OutputType,
29
+ TriggerType: () => TriggerType,
30
+ VariableType: () => VariableType,
31
+ builtinPlugins: () => builtinPlugins,
32
+ clearPlugins: () => clearPlugins,
33
+ compile: () => compile,
34
+ createPluginRegistry: () => createPluginRegistry,
35
+ decompile: () => decompile,
36
+ detectFormat: () => detectFormat,
37
+ formatDiff: () => formatDiff,
38
+ formatSecurityReport: () => formatSecurityReport,
39
+ formatTraceResults: () => formatTraceResults,
40
+ getAllPlugins: () => getAllPlugins,
41
+ getAvailablePlatforms: () => getAvailablePlatforms,
42
+ getPlatform: () => getPlatform,
43
+ getPlugin: () => getPlugin,
44
+ hasPlugin: () => hasPlugin,
45
+ inferFlowStateTypes: () => inferFlowStateTypes,
46
+ installFlowTracer: () => installFlowTracer,
47
+ loadFlowProject: () => loadFlowProject,
48
+ mergeIR: () => mergeIR,
49
+ migrateToSplit: () => migrateToSplit,
50
+ nodeRegistry: () => nodeRegistry,
51
+ parseExpression: () => parseExpression,
52
+ registerPlatform: () => registerPlatform,
53
+ registerPlugin: () => registerPlugin,
54
+ registerPlugins: () => registerPlugins,
55
+ saveFlowProject: () => saveFlowProject,
56
+ semanticDiff: () => semanticDiff,
57
+ splitIR: () => splitIR,
58
+ topologicalSort: () => topologicalSort,
59
+ traceError: () => traceError,
60
+ traceLineToNode: () => traceLineToNode,
61
+ validate: () => validateFlowIR,
62
+ validateFlowIR: () => validateFlowIR,
63
+ validateIRSecurity: () => validateIRSecurity,
64
+ withFlowTrace: () => withFlowTrace
65
+ });
66
+ module.exports = __toCommonJS(lib_exports);
67
+
68
+ // src/lib/compiler/compiler.ts
69
+ var import_ts_morph = require("ts-morph");
70
+
71
+ // src/lib/ir/types.ts
72
+ var CURRENT_IR_VERSION = "1.0.0";
73
+ var NodeCategory = /* @__PURE__ */ ((NodeCategory2) => {
74
+ NodeCategory2["TRIGGER"] = "trigger";
75
+ NodeCategory2["ACTION"] = "action";
76
+ NodeCategory2["LOGIC"] = "logic";
77
+ NodeCategory2["VARIABLE"] = "variable";
78
+ NodeCategory2["OUTPUT"] = "output";
79
+ return NodeCategory2;
80
+ })(NodeCategory || {});
81
+ var TriggerType = /* @__PURE__ */ ((TriggerType2) => {
82
+ TriggerType2["HTTP_WEBHOOK"] = "http_webhook";
83
+ TriggerType2["CRON_JOB"] = "cron_job";
84
+ TriggerType2["MANUAL"] = "manual";
85
+ return TriggerType2;
86
+ })(TriggerType || {});
87
+ var ActionType = /* @__PURE__ */ ((ActionType2) => {
88
+ ActionType2["FETCH_API"] = "fetch_api";
89
+ ActionType2["SQL_QUERY"] = "sql_query";
90
+ ActionType2["REDIS_CACHE"] = "redis_cache";
91
+ ActionType2["CUSTOM_CODE"] = "custom_code";
92
+ ActionType2["CALL_SUBFLOW"] = "call_subflow";
93
+ return ActionType2;
94
+ })(ActionType || {});
95
+ var LogicType = /* @__PURE__ */ ((LogicType2) => {
96
+ LogicType2["IF_ELSE"] = "if_else";
97
+ LogicType2["FOR_LOOP"] = "for_loop";
98
+ LogicType2["TRY_CATCH"] = "try_catch";
99
+ LogicType2["PROMISE_ALL"] = "promise_all";
100
+ return LogicType2;
101
+ })(LogicType || {});
102
+ var VariableType = /* @__PURE__ */ ((VariableType2) => {
103
+ VariableType2["DECLARE"] = "declare";
104
+ VariableType2["TRANSFORM"] = "transform";
105
+ return VariableType2;
106
+ })(VariableType || {});
107
+ var OutputType = /* @__PURE__ */ ((OutputType2) => {
108
+ OutputType2["RETURN_RESPONSE"] = "return_response";
109
+ return OutputType2;
110
+ })(OutputType || {});
111
+
112
+ // src/lib/ir/migrations/engine.ts
113
+ var migrations = [];
114
+ function migrateIR(raw, targetVersion = CURRENT_IR_VERSION) {
115
+ const applied = [];
116
+ let current = { ...raw };
117
+ if (current.version === targetVersion) {
118
+ return { ir: current, applied, migrated: false };
119
+ }
120
+ const maxIterations = migrations.length + 1;
121
+ let iterations = 0;
122
+ while (current.version !== targetVersion) {
123
+ if (iterations++ > maxIterations) {
124
+ throw new MigrationError(
125
+ `Migration exceeded max iterations (${maxIterations}), possible circular migration`,
126
+ current.version,
127
+ targetVersion
128
+ );
129
+ }
130
+ const migration = migrations.find(
131
+ (m) => m.fromVersion === current.version
132
+ );
133
+ if (!migration) {
134
+ throw new MigrationError(
135
+ `No migration path found from ${current.version} to ${targetVersion}`,
136
+ current.version,
137
+ targetVersion
138
+ );
139
+ }
140
+ current = migration.migrate(current);
141
+ applied.push(
142
+ `${migration.fromVersion} \u2192 ${migration.toVersion}: ${migration.description}`
143
+ );
144
+ }
145
+ return { ir: current, applied, migrated: true };
146
+ }
147
+ function needsMigration(version, targetVersion = CURRENT_IR_VERSION) {
148
+ return version !== targetVersion;
149
+ }
150
+ var MigrationError = class extends Error {
151
+ constructor(message, fromVersion, targetVersion) {
152
+ super(message);
153
+ this.fromVersion = fromVersion;
154
+ this.targetVersion = targetVersion;
155
+ this.name = "MigrationError";
156
+ }
157
+ };
158
+
159
+ // src/lib/ir/validator.ts
160
+ function validateFlowIR(ir) {
161
+ const errors = [];
162
+ if (!ir || typeof ir !== "object") {
163
+ return {
164
+ valid: false,
165
+ errors: [{ code: "INVALID_INPUT", message: "IR input must be a non-null object" }]
166
+ };
167
+ }
168
+ if (!Array.isArray(ir.nodes)) {
169
+ return {
170
+ valid: false,
171
+ errors: [{ code: "MISSING_NODES", message: "IR is missing required 'nodes' array" }]
172
+ };
173
+ }
174
+ if (!Array.isArray(ir.edges)) {
175
+ return {
176
+ valid: false,
177
+ errors: [{ code: "MISSING_EDGES", message: "IR is missing required 'edges' array" }]
178
+ };
179
+ }
180
+ let workingIR = ir;
181
+ let migrated = false;
182
+ let migrationLog;
183
+ if (needsMigration(ir.version)) {
184
+ try {
185
+ const result = migrateIR(
186
+ { version: ir.version, meta: ir.meta, nodes: ir.nodes, edges: ir.edges },
187
+ CURRENT_IR_VERSION
188
+ );
189
+ if (result.migrated) {
190
+ workingIR = result.ir;
191
+ migrated = true;
192
+ migrationLog = result.applied;
193
+ }
194
+ } catch (err) {
195
+ if (err instanceof MigrationError) {
196
+ errors.push({
197
+ code: "MIGRATION_FAILED",
198
+ message: `IR version migration failed: ${err.message}`
199
+ });
200
+ } else {
201
+ errors.push({
202
+ code: "INVALID_VERSION",
203
+ message: `Unsupported IR version: ${ir.version}`
204
+ });
205
+ }
206
+ }
207
+ }
208
+ if (!migrated && workingIR.version !== CURRENT_IR_VERSION) {
209
+ errors.push({
210
+ code: "INVALID_VERSION",
211
+ message: `Unsupported IR version: ${workingIR.version} (current: ${CURRENT_IR_VERSION})`
212
+ });
213
+ }
214
+ const workingNodeMap = new Map(workingIR.nodes.map((n) => [n.id, n]));
215
+ const triggers = workingIR.nodes.filter(
216
+ (n) => n.category === "trigger" /* TRIGGER */
217
+ );
218
+ if (triggers.length === 0) {
219
+ errors.push({
220
+ code: "NO_TRIGGER",
221
+ message: "Workflow must contain at least one trigger node"
222
+ });
223
+ }
224
+ if (triggers.length > 1) {
225
+ errors.push({
226
+ code: "MULTIPLE_TRIGGERS",
227
+ message: `Workflow must have exactly one trigger, found ${triggers.length}`
228
+ });
229
+ }
230
+ const idSet = /* @__PURE__ */ new Set();
231
+ for (const node of workingIR.nodes) {
232
+ if (idSet.has(node.id)) {
233
+ errors.push({
234
+ code: "DUPLICATE_NODE_ID",
235
+ message: `Duplicate node ID: ${node.id}`,
236
+ nodeId: node.id
237
+ });
238
+ }
239
+ idSet.add(node.id);
240
+ }
241
+ for (const edge of workingIR.edges) {
242
+ if (!workingNodeMap.has(edge.sourceNodeId)) {
243
+ errors.push({
244
+ code: "INVALID_EDGE_SOURCE",
245
+ message: `Edge "${edge.id}" references non-existent source node "${edge.sourceNodeId}"`,
246
+ edgeId: edge.id
247
+ });
248
+ }
249
+ if (!workingNodeMap.has(edge.targetNodeId)) {
250
+ errors.push({
251
+ code: "INVALID_EDGE_TARGET",
252
+ message: `Edge "${edge.id}" references non-existent target node "${edge.targetNodeId}"`,
253
+ edgeId: edge.id
254
+ });
255
+ }
256
+ }
257
+ const cycleErrors = detectCycles(workingIR.nodes, workingIR.edges);
258
+ errors.push(...cycleErrors);
259
+ const connectedNodes = /* @__PURE__ */ new Set();
260
+ for (const edge of workingIR.edges) {
261
+ connectedNodes.add(edge.sourceNodeId);
262
+ connectedNodes.add(edge.targetNodeId);
263
+ }
264
+ for (const node of workingIR.nodes) {
265
+ if (node.category !== "trigger" /* TRIGGER */ && !connectedNodes.has(node.id)) {
266
+ errors.push({
267
+ code: "ORPHAN_NODE",
268
+ message: `Node "${node.id}" (${node.label}) is not connected to any other node`,
269
+ nodeId: node.id
270
+ });
271
+ }
272
+ }
273
+ return {
274
+ valid: errors.length === 0,
275
+ errors,
276
+ migrated,
277
+ migratedIR: migrated ? workingIR : void 0,
278
+ migrationLog
279
+ };
280
+ }
281
+ function detectCycles(nodes, edges) {
282
+ const errors = [];
283
+ const adjacency = /* @__PURE__ */ new Map();
284
+ for (const node of nodes) {
285
+ adjacency.set(node.id, []);
286
+ }
287
+ for (const edge of edges) {
288
+ adjacency.get(edge.sourceNodeId)?.push(edge.targetNodeId);
289
+ }
290
+ const WHITE = 0;
291
+ const GRAY = 1;
292
+ const BLACK = 2;
293
+ const color = /* @__PURE__ */ new Map();
294
+ for (const node of nodes) {
295
+ color.set(node.id, WHITE);
296
+ }
297
+ function dfs(nodeId) {
298
+ color.set(nodeId, GRAY);
299
+ for (const neighbor of adjacency.get(nodeId) ?? []) {
300
+ if (color.get(neighbor) === GRAY) {
301
+ errors.push({
302
+ code: "CYCLE_DETECTED",
303
+ message: `Cycle detected: node "${nodeId}" \u2192 "${neighbor}"`,
304
+ nodeId
305
+ });
306
+ } else if (color.get(neighbor) === WHITE) {
307
+ dfs(neighbor);
308
+ }
309
+ }
310
+ color.set(nodeId, BLACK);
311
+ }
312
+ for (const node of nodes) {
313
+ if (color.get(node.id) === WHITE) {
314
+ dfs(node.id);
315
+ }
316
+ }
317
+ return errors;
318
+ }
319
+
320
+ // src/lib/ir/topological-sort.ts
321
+ function buildGraph(nodes, edges) {
322
+ const indegree = /* @__PURE__ */ new Map();
323
+ const adjacency = /* @__PURE__ */ new Map();
324
+ const reverseAdjacency = /* @__PURE__ */ new Map();
325
+ for (const node of nodes) {
326
+ indegree.set(node.id, 0);
327
+ adjacency.set(node.id, /* @__PURE__ */ new Set());
328
+ reverseAdjacency.set(node.id, /* @__PURE__ */ new Set());
329
+ }
330
+ for (const edge of edges) {
331
+ adjacency.get(edge.sourceNodeId).add(edge.targetNodeId);
332
+ reverseAdjacency.get(edge.targetNodeId).add(edge.sourceNodeId);
333
+ indegree.set(
334
+ edge.targetNodeId,
335
+ (indegree.get(edge.targetNodeId) ?? 0) + 1
336
+ );
337
+ }
338
+ return { indegree, adjacency, reverseAdjacency };
339
+ }
340
+ function topologicalSort(ir) {
341
+ const { nodes, edges } = ir;
342
+ const { indegree, adjacency, reverseAdjacency } = buildGraph(nodes, edges);
343
+ const indegreeCopy = new Map(indegree);
344
+ const sortedNodeIds = [];
345
+ const steps = [];
346
+ let currentLevel = [];
347
+ for (const [nodeId, degree] of indegreeCopy) {
348
+ if (degree === 0) {
349
+ currentLevel.push(nodeId);
350
+ }
351
+ }
352
+ let stepIndex = 0;
353
+ while (currentLevel.length > 0) {
354
+ steps.push({
355
+ index: stepIndex,
356
+ nodeIds: [...currentLevel],
357
+ concurrent: currentLevel.length > 1
358
+ });
359
+ sortedNodeIds.push(...currentLevel);
360
+ const nextLevel = [];
361
+ for (const nodeId of currentLevel) {
362
+ for (const neighbor of adjacency.get(nodeId) ?? []) {
363
+ const newDegree = (indegreeCopy.get(neighbor) ?? 1) - 1;
364
+ indegreeCopy.set(neighbor, newDegree);
365
+ if (newDegree === 0) {
366
+ nextLevel.push(neighbor);
367
+ }
368
+ }
369
+ }
370
+ currentLevel = nextLevel;
371
+ stepIndex++;
372
+ }
373
+ const dependencies = /* @__PURE__ */ new Map();
374
+ const dependents = /* @__PURE__ */ new Map();
375
+ for (const node of nodes) {
376
+ dependencies.set(node.id, reverseAdjacency.get(node.id) ?? /* @__PURE__ */ new Set());
377
+ dependents.set(node.id, adjacency.get(node.id) ?? /* @__PURE__ */ new Set());
378
+ }
379
+ if (sortedNodeIds.length !== nodes.length) {
380
+ throw new Error(
381
+ `Topological sort failed: cycle detected in graph. Sorted ${sortedNodeIds.length}/${nodes.length} nodes.`
382
+ );
383
+ }
384
+ return {
385
+ steps,
386
+ sortedNodeIds,
387
+ dependencies,
388
+ dependents
389
+ };
390
+ }
391
+
392
+ // src/lib/compiler/expression-parser.ts
393
+ function parseExpression(expr, context) {
394
+ const tokens = tokenize(expr);
395
+ return tokens.map((token) => {
396
+ if (token.type === "literal") return token.value;
397
+ return resolveReference(parseReference(token.value), context);
398
+ }).join("");
399
+ }
400
+ function tokenize(input) {
401
+ const tokens = [];
402
+ let i = 0;
403
+ let literalBuf = "";
404
+ while (i < input.length) {
405
+ if (input[i] === "\\" && input[i + 1] === "{" && input[i + 2] === "{") {
406
+ literalBuf += "{{";
407
+ i += 3;
408
+ continue;
409
+ }
410
+ if (input[i] === "{" && input[i + 1] === "{") {
411
+ if (literalBuf) {
412
+ tokens.push({ type: "literal", value: literalBuf });
413
+ literalBuf = "";
414
+ }
415
+ i += 2;
416
+ const refStart = i;
417
+ let bracketDepth = 0;
418
+ while (i < input.length) {
419
+ if (input[i] === "[") {
420
+ bracketDepth++;
421
+ i++;
422
+ } else if (input[i] === "]") {
423
+ bracketDepth--;
424
+ i++;
425
+ } else if (input[i] === "}" && input[i + 1] === "}" && bracketDepth === 0) {
426
+ break;
427
+ } else {
428
+ i++;
429
+ }
430
+ }
431
+ if (i >= input.length) {
432
+ throw new ExpressionParseError(
433
+ `Unclosed template expression: missing matching '}}' (at position ${refStart - 2})`,
434
+ input,
435
+ refStart - 2
436
+ );
437
+ }
438
+ const refContent = input.slice(refStart, i).trim();
439
+ if (!refContent) {
440
+ throw new ExpressionParseError(
441
+ "Empty template expression {{}}",
442
+ input,
443
+ refStart - 2
444
+ );
445
+ }
446
+ tokens.push({ type: "reference", value: refContent });
447
+ i += 2;
448
+ continue;
449
+ }
450
+ literalBuf += input[i];
451
+ i++;
452
+ }
453
+ if (literalBuf) {
454
+ tokens.push({ type: "literal", value: literalBuf });
455
+ }
456
+ return tokens;
457
+ }
458
+ function parseReference(ref) {
459
+ const match = ref.match(/^(\$?\w+)((?:\.[\w]+|\[.+?\])*)$/);
460
+ if (!match) {
461
+ const dotIndex = ref.indexOf(".");
462
+ const bracketIndex = ref.indexOf("[");
463
+ let splitAt = -1;
464
+ if (dotIndex !== -1 && bracketIndex !== -1) {
465
+ splitAt = Math.min(dotIndex, bracketIndex);
466
+ } else if (dotIndex !== -1) {
467
+ splitAt = dotIndex;
468
+ } else if (bracketIndex !== -1) {
469
+ splitAt = bracketIndex;
470
+ }
471
+ if (splitAt > 0) {
472
+ return {
473
+ base: ref.slice(0, splitAt),
474
+ path: ref.slice(splitAt)
475
+ };
476
+ }
477
+ return { base: ref, path: "" };
478
+ }
479
+ return {
480
+ base: match[1],
481
+ path: match[2] || ""
482
+ };
483
+ }
484
+ function resolveReference(ref, context) {
485
+ const { base, path } = ref;
486
+ if (base === "$input") {
487
+ return resolveInputRef(path, context);
488
+ }
489
+ if (base === "$trigger") {
490
+ return resolveTriggerRef(path, context);
491
+ }
492
+ if (context.scopeStack) {
493
+ for (let i = context.scopeStack.length - 1; i >= 0; i--) {
494
+ const scope = context.scopeStack[i];
495
+ if (scope.nodeId === base) {
496
+ return `${scope.scopeVar}['${base}']${path}`;
497
+ }
498
+ }
499
+ }
500
+ if (context.blockScopedNodeIds?.has(base)) {
501
+ return `flowState['${base}']${path}`;
502
+ }
503
+ if (context.symbolTable?.hasVar(base)) {
504
+ return `${context.symbolTable.getVarName(base)}${path}`;
505
+ }
506
+ return `flowState['${base}']${path}`;
507
+ }
508
+ function resolveInputRef(path, context) {
509
+ if (!context.currentNodeId) {
510
+ throw new Error(
511
+ `Expression parser error: No current node context for $input reference`
512
+ );
513
+ }
514
+ const incoming = context.ir.edges.filter(
515
+ (e) => e.targetNodeId === context.currentNodeId
516
+ );
517
+ const dataSource = incoming.find((e) => {
518
+ const src = context.nodeMap.get(e.sourceNodeId);
519
+ return src && src.category !== "trigger" /* TRIGGER */;
520
+ }) || incoming[0];
521
+ if (dataSource) {
522
+ const srcId = dataSource.sourceNodeId;
523
+ if (context.blockScopedNodeIds?.has(srcId)) {
524
+ return `flowState['${srcId}']${path}`;
525
+ }
526
+ if (context.symbolTable?.hasVar(srcId)) {
527
+ return `${context.symbolTable.getVarName(srcId)}${path}`;
528
+ }
529
+ return `flowState['${srcId}']${path}`;
530
+ }
531
+ throw new Error(
532
+ `Expression parser error: Node "${context.currentNodeId}" has no input connected`
533
+ );
534
+ }
535
+ function resolveTriggerRef(path, context) {
536
+ const trigger = context.ir.nodes.find(
537
+ (n) => n.category === "trigger" /* TRIGGER */
538
+ );
539
+ if (trigger) {
540
+ if (context.symbolTable?.hasVar(trigger.id)) {
541
+ return `${context.symbolTable.getVarName(trigger.id)}${path}`;
542
+ }
543
+ return `flowState['${trigger.id}']${path}`;
544
+ }
545
+ return "undefined";
546
+ }
547
+ var ExpressionParseError = class extends Error {
548
+ constructor(message, expression, position) {
549
+ super(message);
550
+ this.expression = expression;
551
+ this.position = position;
552
+ this.name = "ExpressionParseError";
553
+ }
554
+ };
555
+
556
+ // src/lib/compiler/platforms/types.ts
557
+ var platformRegistry = /* @__PURE__ */ new Map();
558
+ function registerPlatform(name, factory) {
559
+ platformRegistry.set(name, factory);
560
+ }
561
+ function getPlatform(name) {
562
+ const factory = platformRegistry.get(name);
563
+ if (!factory) {
564
+ throw new Error(
565
+ `Unknown platform "${name}". Available platforms: ${[...platformRegistry.keys()].join(", ")}`
566
+ );
567
+ }
568
+ return factory();
569
+ }
570
+ function getAvailablePlatforms() {
571
+ return [...platformRegistry.keys()];
572
+ }
573
+
574
+ // src/lib/compiler/platforms/nextjs.ts
575
+ var NextjsPlatform = class {
576
+ name = "nextjs";
577
+ generateImports(sourceFile, trigger, _context) {
578
+ if (trigger.nodeType !== "http_webhook" /* HTTP_WEBHOOK */) return;
579
+ const params = trigger.params;
580
+ const isGetOrDelete = ["GET", "DELETE"].includes(params.method);
581
+ sourceFile.addImportDeclaration({
582
+ namedImports: isGetOrDelete ? ["NextRequest", "NextResponse"] : ["NextResponse"],
583
+ moduleSpecifier: "next/server"
584
+ });
585
+ }
586
+ generateFunction(sourceFile, trigger, _context, bodyGenerator) {
587
+ switch (trigger.nodeType) {
588
+ case "http_webhook" /* HTTP_WEBHOOK */:
589
+ this.generateHttpFunction(sourceFile, trigger, bodyGenerator);
590
+ break;
591
+ case "cron_job" /* CRON_JOB */:
592
+ this.generateCronFunction(sourceFile, trigger, bodyGenerator);
593
+ break;
594
+ case "manual" /* MANUAL */:
595
+ this.generateManualFunction(sourceFile, trigger, bodyGenerator);
596
+ break;
597
+ default:
598
+ throw new Error(`Unsupported trigger type: ${trigger.nodeType}`);
599
+ }
600
+ }
601
+ generateResponse(writer, bodyExpr, statusCode, headers) {
602
+ if (headers && Object.keys(headers).length > 0) {
603
+ writer.writeLine(
604
+ `throw new EarlyResponse(NextResponse.json(${bodyExpr}, { status: ${statusCode}, headers: ${JSON.stringify(headers)} }));`
605
+ );
606
+ } else {
607
+ writer.writeLine(
608
+ `throw new EarlyResponse(NextResponse.json(${bodyExpr}, { status: ${statusCode} }));`
609
+ );
610
+ }
611
+ }
612
+ generateErrorResponse(writer) {
613
+ writer.write("if (error instanceof EarlyResponse) ").block(() => {
614
+ writer.writeLine("return error.response;");
615
+ });
616
+ writer.writeLine('console.error("Workflow failed:", error);');
617
+ writer.writeLine(
618
+ 'return NextResponse.json({ error: error instanceof Error ? error.message : "Internal Server Error" }, { status: 500 });'
619
+ );
620
+ }
621
+ getOutputFilePath(trigger) {
622
+ if (trigger.nodeType === "http_webhook" /* HTTP_WEBHOOK */) {
623
+ const params = trigger.params;
624
+ const routePath = params.routePath.replace(/^\//, "");
625
+ return `src/app/${routePath}/route.ts`;
626
+ }
627
+ if (trigger.nodeType === "cron_job" /* CRON_JOB */) {
628
+ const params = trigger.params;
629
+ return `src/lib/cron/${params.functionName}.ts`;
630
+ }
631
+ if (trigger.nodeType === "manual" /* MANUAL */) {
632
+ const params = trigger.params;
633
+ return `src/lib/functions/${params.functionName}.ts`;
634
+ }
635
+ return "src/generated/flow.ts";
636
+ }
637
+ getImplicitDependencies() {
638
+ return [];
639
+ }
640
+ generateTriggerInit(writer, trigger, context) {
641
+ const varName = context.symbolTable.getVarName(trigger.id);
642
+ switch (trigger.nodeType) {
643
+ case "http_webhook" /* HTTP_WEBHOOK */: {
644
+ const params = trigger.params;
645
+ const isGetOrDelete = ["GET", "DELETE"].includes(params.method);
646
+ if (isGetOrDelete) {
647
+ writer.writeLine("const searchParams = req.nextUrl.searchParams;");
648
+ writer.writeLine(
649
+ "const query = Object.fromEntries(searchParams.entries());"
650
+ );
651
+ writer.writeLine(`const ${varName} = { query, url: req.url };`);
652
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
653
+ } else if (params.parseBody && ["POST", "PUT", "PATCH"].includes(params.method)) {
654
+ writer.writeLine("let body: unknown;");
655
+ writer.write("try ").block(() => {
656
+ writer.writeLine("body = await req.json();");
657
+ });
658
+ writer.write(" catch ").block(() => {
659
+ writer.writeLine(
660
+ 'return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });'
661
+ );
662
+ });
663
+ writer.writeLine(`const ${varName} = { body, url: req.url };`);
664
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
665
+ } else {
666
+ writer.writeLine(`const ${varName} = { url: req.url };`);
667
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
668
+ }
669
+ break;
670
+ }
671
+ case "cron_job" /* CRON_JOB */: {
672
+ writer.writeLine(
673
+ `const ${varName} = { triggeredAt: new Date().toISOString() };`
674
+ );
675
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
676
+ break;
677
+ }
678
+ case "manual" /* MANUAL */: {
679
+ const params = trigger.params;
680
+ if (params.args.length > 0) {
681
+ const argsObj = params.args.map((a) => a.name).join(", ");
682
+ writer.writeLine(`const ${varName} = { ${argsObj} };`);
683
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
684
+ }
685
+ break;
686
+ }
687
+ }
688
+ }
689
+ // ── Private ──
690
+ generateHttpFunction(sourceFile, trigger, bodyGenerator) {
691
+ const params = trigger.params;
692
+ const isGetOrDelete = ["GET", "DELETE"].includes(params.method);
693
+ const funcDecl = sourceFile.addFunction({
694
+ name: params.method,
695
+ isAsync: true,
696
+ isExported: true,
697
+ parameters: [
698
+ { name: "req", type: isGetOrDelete ? "NextRequest" : "Request" }
699
+ ]
700
+ });
701
+ sourceFile.insertStatements(funcDecl.getChildIndex(), (writer) => {
702
+ writer.writeLine("class EarlyResponse extends Error {");
703
+ writer.writeLine(" constructor(public response: Response) { super(); }");
704
+ writer.writeLine("}");
705
+ writer.blankLine();
706
+ });
707
+ funcDecl.addStatements((writer) => {
708
+ writer.write("try ").block(() => {
709
+ bodyGenerator(writer);
710
+ });
711
+ writer.write(" catch (error) ").block(() => {
712
+ this.generateErrorResponse(writer);
713
+ });
714
+ });
715
+ }
716
+ generateCronFunction(sourceFile, trigger, bodyGenerator) {
717
+ const params = trigger.params;
718
+ sourceFile.addStatements(`// @schedule ${params.schedule}`);
719
+ const funcDecl = sourceFile.addFunction({
720
+ name: params.functionName,
721
+ isAsync: true,
722
+ isExported: true
723
+ });
724
+ funcDecl.addStatements((writer) => {
725
+ bodyGenerator(writer);
726
+ });
727
+ }
728
+ generateManualFunction(sourceFile, trigger, bodyGenerator) {
729
+ const params = trigger.params;
730
+ const funcDecl = sourceFile.addFunction({
731
+ name: params.functionName,
732
+ isAsync: true,
733
+ isExported: true,
734
+ parameters: params.args.map((arg) => ({
735
+ name: arg.name,
736
+ type: arg.type
737
+ }))
738
+ });
739
+ funcDecl.addStatements((writer) => {
740
+ bodyGenerator(writer);
741
+ });
742
+ }
743
+ };
744
+
745
+ // src/lib/compiler/platforms/express.ts
746
+ var ExpressPlatform = class {
747
+ name = "express";
748
+ generateImports(sourceFile, trigger, _context) {
749
+ if (trigger.nodeType === "http_webhook" /* HTTP_WEBHOOK */) {
750
+ sourceFile.addImportDeclaration({
751
+ namedImports: ["Request", "Response"],
752
+ moduleSpecifier: "express"
753
+ });
754
+ }
755
+ }
756
+ generateFunction(sourceFile, trigger, _context, bodyGenerator) {
757
+ switch (trigger.nodeType) {
758
+ case "http_webhook" /* HTTP_WEBHOOK */:
759
+ this.generateHttpHandler(sourceFile, trigger, bodyGenerator);
760
+ break;
761
+ case "cron_job" /* CRON_JOB */:
762
+ this.generateCronFunction(sourceFile, trigger, bodyGenerator);
763
+ break;
764
+ case "manual" /* MANUAL */:
765
+ this.generateManualFunction(sourceFile, trigger, bodyGenerator);
766
+ break;
767
+ default:
768
+ throw new Error(`Unsupported trigger type: ${trigger.nodeType}`);
769
+ }
770
+ }
771
+ generateResponse(writer, bodyExpr, statusCode, headers) {
772
+ if (headers && Object.keys(headers).length > 0) {
773
+ for (const [key, value] of Object.entries(headers)) {
774
+ writer.writeLine(`res.setHeader(${JSON.stringify(key)}, ${JSON.stringify(value)});`);
775
+ }
776
+ }
777
+ writer.writeLine(`throw new EarlyResponse(() => res.status(${statusCode}).json(${bodyExpr}));`);
778
+ }
779
+ generateErrorResponse(writer) {
780
+ writer.write("if (error instanceof EarlyResponse) ").block(() => {
781
+ writer.writeLine("return error.send();");
782
+ });
783
+ writer.writeLine('console.error("Workflow failed:", error);');
784
+ writer.writeLine(
785
+ 'return res.status(500).json({ error: error instanceof Error ? error.message : "Internal Server Error" });'
786
+ );
787
+ }
788
+ getOutputFilePath(trigger) {
789
+ if (trigger.nodeType === "http_webhook" /* HTTP_WEBHOOK */) {
790
+ const params = trigger.params;
791
+ const routePath = params.routePath.replace(/^\//, "").replace(/\//g, "-");
792
+ return `src/routes/${routePath}.ts`;
793
+ }
794
+ if (trigger.nodeType === "cron_job" /* CRON_JOB */) {
795
+ const params = trigger.params;
796
+ return `src/cron/${params.functionName}.ts`;
797
+ }
798
+ if (trigger.nodeType === "manual" /* MANUAL */) {
799
+ const params = trigger.params;
800
+ return `src/functions/${params.functionName}.ts`;
801
+ }
802
+ return "src/generated/flow.ts";
803
+ }
804
+ getImplicitDependencies() {
805
+ return ["express", "@types/express"];
806
+ }
807
+ generateTriggerInit(writer, trigger, context) {
808
+ const varName = context.symbolTable.getVarName(trigger.id);
809
+ switch (trigger.nodeType) {
810
+ case "http_webhook" /* HTTP_WEBHOOK */: {
811
+ const params = trigger.params;
812
+ const isGetOrDelete = ["GET", "DELETE"].includes(params.method);
813
+ if (isGetOrDelete) {
814
+ writer.writeLine(
815
+ "const query = req.query as Record<string, string>;"
816
+ );
817
+ writer.writeLine(
818
+ `const ${varName} = { query, url: req.originalUrl };`
819
+ );
820
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
821
+ } else if (params.parseBody && ["POST", "PUT", "PATCH"].includes(params.method)) {
822
+ writer.writeLine("const body = req.body;");
823
+ writer.writeLine(
824
+ `const ${varName} = { body, url: req.originalUrl };`
825
+ );
826
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
827
+ } else {
828
+ writer.writeLine(
829
+ `const ${varName} = { url: req.originalUrl };`
830
+ );
831
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
832
+ }
833
+ break;
834
+ }
835
+ case "cron_job" /* CRON_JOB */: {
836
+ writer.writeLine(
837
+ `const ${varName} = { triggeredAt: new Date().toISOString() };`
838
+ );
839
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
840
+ break;
841
+ }
842
+ case "manual" /* MANUAL */: {
843
+ const params = trigger.params;
844
+ if (params.args.length > 0) {
845
+ const argsObj = params.args.map((a) => a.name).join(", ");
846
+ writer.writeLine(`const ${varName} = { ${argsObj} };`);
847
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
848
+ }
849
+ break;
850
+ }
851
+ }
852
+ }
853
+ // ── Private ──
854
+ generateHttpHandler(sourceFile, trigger, bodyGenerator) {
855
+ const params = trigger.params;
856
+ const funcDecl = sourceFile.addFunction({
857
+ name: `handle${params.method.charAt(0)}${params.method.slice(1).toLowerCase()}`,
858
+ isAsync: true,
859
+ isExported: true,
860
+ parameters: [
861
+ { name: "req", type: "Request" },
862
+ { name: "res", type: "Response" }
863
+ ]
864
+ });
865
+ sourceFile.insertStatements(funcDecl.getChildIndex(), (writer) => {
866
+ writer.writeLine("class EarlyResponse extends Error {");
867
+ writer.writeLine(" constructor(public send: () => void) { super(); }");
868
+ writer.writeLine("}");
869
+ writer.blankLine();
870
+ });
871
+ funcDecl.addStatements((writer) => {
872
+ writer.write("try ").block(() => {
873
+ bodyGenerator(writer);
874
+ });
875
+ writer.write(" catch (error) ").block(() => {
876
+ this.generateErrorResponse(writer);
877
+ });
878
+ });
879
+ }
880
+ generateCronFunction(sourceFile, trigger, bodyGenerator) {
881
+ const params = trigger.params;
882
+ sourceFile.addStatements(`// @schedule ${params.schedule}`);
883
+ const funcDecl = sourceFile.addFunction({
884
+ name: params.functionName,
885
+ isAsync: true,
886
+ isExported: true
887
+ });
888
+ funcDecl.addStatements((writer) => {
889
+ bodyGenerator(writer);
890
+ });
891
+ }
892
+ generateManualFunction(sourceFile, trigger, bodyGenerator) {
893
+ const params = trigger.params;
894
+ const funcDecl = sourceFile.addFunction({
895
+ name: params.functionName,
896
+ isAsync: true,
897
+ isExported: true,
898
+ parameters: params.args.map((arg) => ({
899
+ name: arg.name,
900
+ type: arg.type
901
+ }))
902
+ });
903
+ funcDecl.addStatements((writer) => {
904
+ bodyGenerator(writer);
905
+ });
906
+ }
907
+ };
908
+
909
+ // src/lib/compiler/platforms/cloudflare.ts
910
+ var CloudflarePlatform = class {
911
+ name = "cloudflare";
912
+ generateImports(_sourceFile, _trigger, _context) {
913
+ }
914
+ generateFunction(sourceFile, trigger, _context, bodyGenerator) {
915
+ switch (trigger.nodeType) {
916
+ case "http_webhook" /* HTTP_WEBHOOK */:
917
+ this.generateFetchHandler(sourceFile, trigger, bodyGenerator);
918
+ break;
919
+ case "cron_job" /* CRON_JOB */:
920
+ this.generateScheduledHandler(sourceFile, trigger, bodyGenerator);
921
+ break;
922
+ case "manual" /* MANUAL */:
923
+ this.generateManualFunction(sourceFile, trigger, bodyGenerator);
924
+ break;
925
+ default:
926
+ throw new Error(`Unsupported trigger type: ${trigger.nodeType}`);
927
+ }
928
+ }
929
+ generateResponse(writer, bodyExpr, statusCode, headers) {
930
+ const headersObj = headers && Object.keys(headers).length > 0 ? `, { status: ${statusCode}, headers: ${JSON.stringify({ "Content-Type": "application/json", ...headers })} }` : `, { status: ${statusCode}, headers: { "Content-Type": "application/json" } }`;
931
+ writer.writeLine(
932
+ `throw new EarlyResponse(new Response(JSON.stringify(${bodyExpr})${headersObj}));`
933
+ );
934
+ }
935
+ generateErrorResponse(writer) {
936
+ writer.write("if (error instanceof EarlyResponse) ").block(() => {
937
+ writer.writeLine("return error.response;");
938
+ });
939
+ writer.writeLine('console.error("Workflow failed:", error);');
940
+ writer.writeLine(
941
+ `return new Response(JSON.stringify({ error: error instanceof Error ? error.message : "Internal Server Error" }), { status: 500, headers: { "Content-Type": "application/json" } });`
942
+ );
943
+ }
944
+ getOutputFilePath(trigger) {
945
+ if (trigger.nodeType === "http_webhook" /* HTTP_WEBHOOK */) {
946
+ return "src/worker.ts";
947
+ }
948
+ if (trigger.nodeType === "cron_job" /* CRON_JOB */) {
949
+ const params = trigger.params;
950
+ return `src/scheduled/${params.functionName}.ts`;
951
+ }
952
+ if (trigger.nodeType === "manual" /* MANUAL */) {
953
+ const params = trigger.params;
954
+ return `src/functions/${params.functionName}.ts`;
955
+ }
956
+ return "src/generated/flow.ts";
957
+ }
958
+ getImplicitDependencies() {
959
+ return ["@cloudflare/workers-types"];
960
+ }
961
+ generateTriggerInit(writer, trigger, context) {
962
+ const varName = context.symbolTable.getVarName(trigger.id);
963
+ switch (trigger.nodeType) {
964
+ case "http_webhook" /* HTTP_WEBHOOK */: {
965
+ const params = trigger.params;
966
+ const isGetOrDelete = ["GET", "DELETE"].includes(params.method);
967
+ if (isGetOrDelete) {
968
+ writer.writeLine(
969
+ "const url = new URL(request.url);"
970
+ );
971
+ writer.writeLine(
972
+ "const query = Object.fromEntries(url.searchParams.entries());"
973
+ );
974
+ writer.writeLine(
975
+ `const ${varName} = { query, url: request.url };`
976
+ );
977
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
978
+ } else if (params.parseBody && ["POST", "PUT", "PATCH"].includes(params.method)) {
979
+ writer.writeLine("let body: unknown;");
980
+ writer.write("try ").block(() => {
981
+ writer.writeLine("body = await request.json();");
982
+ });
983
+ writer.write(" catch ").block(() => {
984
+ writer.writeLine(
985
+ 'return new Response(JSON.stringify({ error: "Invalid JSON body" }), { status: 400, headers: { "Content-Type": "application/json" } });'
986
+ );
987
+ });
988
+ writer.writeLine(
989
+ `const ${varName} = { body, url: request.url };`
990
+ );
991
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
992
+ } else {
993
+ writer.writeLine(
994
+ `const ${varName} = { url: request.url };`
995
+ );
996
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
997
+ }
998
+ break;
999
+ }
1000
+ case "cron_job" /* CRON_JOB */: {
1001
+ writer.writeLine(
1002
+ `const ${varName} = { triggeredAt: new Date().toISOString() };`
1003
+ );
1004
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
1005
+ break;
1006
+ }
1007
+ case "manual" /* MANUAL */: {
1008
+ const params = trigger.params;
1009
+ if (params.args.length > 0) {
1010
+ const argsObj = params.args.map((a) => a.name).join(", ");
1011
+ writer.writeLine(`const ${varName} = { ${argsObj} };`);
1012
+ writer.writeLine(`flowState['${trigger.id}'] = ${varName};`);
1013
+ }
1014
+ break;
1015
+ }
1016
+ }
1017
+ }
1018
+ // ── Private ──
1019
+ generateFetchHandler(sourceFile, trigger, bodyGenerator) {
1020
+ const params = trigger.params;
1021
+ sourceFile.addStatements(`// Cloudflare Workers handler`);
1022
+ const funcDecl = sourceFile.addFunction({
1023
+ name: "handleRequest",
1024
+ isAsync: true,
1025
+ isExported: true,
1026
+ parameters: [
1027
+ { name: "request", type: "Request" },
1028
+ { name: "env", type: "Env" },
1029
+ { name: "ctx", type: "ExecutionContext" }
1030
+ ]
1031
+ });
1032
+ sourceFile.insertStatements(funcDecl.getChildIndex(), (writer) => {
1033
+ writer.writeLine("class EarlyResponse extends Error {");
1034
+ writer.writeLine(" constructor(public response: Response) { super(); }");
1035
+ writer.writeLine("}");
1036
+ writer.blankLine();
1037
+ });
1038
+ funcDecl.addStatements((writer) => {
1039
+ writer.write("try ").block(() => {
1040
+ bodyGenerator(writer);
1041
+ });
1042
+ writer.write(" catch (error) ").block(() => {
1043
+ this.generateErrorResponse(writer);
1044
+ });
1045
+ });
1046
+ sourceFile.addStatements(`
1047
+ export default {
1048
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
1049
+ return handleRequest(request, env, ctx);
1050
+ },
1051
+ };`);
1052
+ }
1053
+ generateScheduledHandler(sourceFile, trigger, bodyGenerator) {
1054
+ const params = trigger.params;
1055
+ sourceFile.addStatements(`// @schedule ${params.schedule}`);
1056
+ const funcDecl = sourceFile.addFunction({
1057
+ name: params.functionName,
1058
+ isAsync: true,
1059
+ isExported: true
1060
+ });
1061
+ funcDecl.addStatements((writer) => {
1062
+ bodyGenerator(writer);
1063
+ });
1064
+ }
1065
+ generateManualFunction(sourceFile, trigger, bodyGenerator) {
1066
+ const params = trigger.params;
1067
+ const funcDecl = sourceFile.addFunction({
1068
+ name: params.functionName,
1069
+ isAsync: true,
1070
+ isExported: true,
1071
+ parameters: params.args.map((arg) => ({
1072
+ name: arg.name,
1073
+ type: arg.type
1074
+ }))
1075
+ });
1076
+ funcDecl.addStatements((writer) => {
1077
+ bodyGenerator(writer);
1078
+ });
1079
+ }
1080
+ };
1081
+
1082
+ // src/lib/compiler/platforms/index.ts
1083
+ registerPlatform("nextjs", () => new NextjsPlatform());
1084
+ registerPlatform("express", () => new ExpressPlatform());
1085
+ registerPlatform("cloudflare", () => new CloudflarePlatform());
1086
+
1087
+ // src/lib/compiler/plugins/types.ts
1088
+ function createPluginRegistry() {
1089
+ const map = /* @__PURE__ */ new Map();
1090
+ return {
1091
+ register(plugin) {
1092
+ map.set(plugin.nodeType, plugin);
1093
+ },
1094
+ registerAll(plugins) {
1095
+ for (const p of plugins) map.set(p.nodeType, p);
1096
+ },
1097
+ get(nodeType) {
1098
+ return map.get(nodeType);
1099
+ },
1100
+ has(nodeType) {
1101
+ return map.has(nodeType);
1102
+ },
1103
+ getAll() {
1104
+ return new Map(map);
1105
+ },
1106
+ clear() {
1107
+ map.clear();
1108
+ }
1109
+ };
1110
+ }
1111
+ var globalRegistry = createPluginRegistry();
1112
+ function registerPlugin(plugin) {
1113
+ globalRegistry.register(plugin);
1114
+ }
1115
+ function registerPlugins(plugins) {
1116
+ globalRegistry.registerAll(plugins);
1117
+ }
1118
+ function getPlugin(nodeType) {
1119
+ return globalRegistry.get(nodeType);
1120
+ }
1121
+ function getAllPlugins() {
1122
+ return globalRegistry.getAll();
1123
+ }
1124
+ function clearPlugins() {
1125
+ globalRegistry.clear();
1126
+ }
1127
+ function hasPlugin(nodeType) {
1128
+ return globalRegistry.has(nodeType);
1129
+ }
1130
+
1131
+ // src/lib/compiler/plugins/builtin.ts
1132
+ function inferTypeFromExpression(expr) {
1133
+ const trimmed = expr.trim();
1134
+ if (/\.map\s*\(/.test(trimmed)) return "unknown[]";
1135
+ if (/\.filter\s*\(/.test(trimmed)) return "unknown[]";
1136
+ if (/\.flatMap\s*\(/.test(trimmed)) return "unknown[]";
1137
+ if (/\.slice\s*\(/.test(trimmed)) return "unknown[]";
1138
+ if (/\.concat\s*\(/.test(trimmed)) return "unknown[]";
1139
+ if (/\.sort\s*\(/.test(trimmed)) return "unknown[]";
1140
+ if (/\.reverse\s*\(/.test(trimmed)) return "unknown[]";
1141
+ if (/Array\.from\s*\(/.test(trimmed)) return "unknown[]";
1142
+ if (/\.flat\s*\(/.test(trimmed)) return "unknown[]";
1143
+ if (/\.entries\s*\(/.test(trimmed)) return "[string, unknown][]";
1144
+ if (/\.reduce\s*\(/.test(trimmed)) return "unknown";
1145
+ if (/\.length\b/.test(trimmed)) return "number";
1146
+ if (/\.indexOf\s*\(/.test(trimmed)) return "number";
1147
+ if (/\.findIndex\s*\(/.test(trimmed)) return "number";
1148
+ if (/parseInt\s*\(/.test(trimmed)) return "number";
1149
+ if (/parseFloat\s*\(/.test(trimmed)) return "number";
1150
+ if (/Number\s*\(/.test(trimmed)) return "number";
1151
+ if (/Math\./.test(trimmed)) return "number";
1152
+ if (/\.includes\s*\(/.test(trimmed)) return "boolean";
1153
+ if (/\.some\s*\(/.test(trimmed)) return "boolean";
1154
+ if (/\.every\s*\(/.test(trimmed)) return "boolean";
1155
+ if (/\.has\s*\(/.test(trimmed)) return "boolean";
1156
+ if (/^!/.test(trimmed)) return "boolean";
1157
+ if (/===|!==|==|!=|>=|<=|>|<|&&|\|\|/.test(trimmed)) return "boolean";
1158
+ if (/\.join\s*\(/.test(trimmed)) return "string";
1159
+ if (/\.toString\s*\(/.test(trimmed)) return "string";
1160
+ if (/\.trim\s*\(/.test(trimmed)) return "string";
1161
+ if (/\.replace\s*\(/.test(trimmed)) return "string";
1162
+ if (/\.toLowerCase\s*\(/.test(trimmed)) return "string";
1163
+ if (/\.toUpperCase\s*\(/.test(trimmed)) return "string";
1164
+ if (/String\s*\(/.test(trimmed)) return "string";
1165
+ if (/JSON\.stringify\s*\(/.test(trimmed)) return "string";
1166
+ if (/`[^`]*`/.test(trimmed)) return "string";
1167
+ if (/JSON\.parse\s*\(/.test(trimmed)) return "unknown";
1168
+ if (/Object\.keys\s*\(/.test(trimmed)) return "string[]";
1169
+ if (/Object\.values\s*\(/.test(trimmed)) return "unknown[]";
1170
+ if (/Object\.entries\s*\(/.test(trimmed)) return "[string, unknown][]";
1171
+ if (/Object\.assign\s*\(/.test(trimmed)) return "Record<string, unknown>";
1172
+ if (/Object\.fromEntries\s*\(/.test(trimmed)) return "Record<string, unknown>";
1173
+ if (/^\{/.test(trimmed)) return "Record<string, unknown>";
1174
+ if (/^\[/.test(trimmed)) return "unknown[]";
1175
+ if (/\.\.\.\s*\{\{/.test(trimmed)) return "Record<string, unknown>";
1176
+ if (/\.find\s*\(/.test(trimmed)) return "unknown | undefined";
1177
+ return "unknown";
1178
+ }
1179
+ function inferTypeFromCode(code, returnVar) {
1180
+ const declMatch = code.match(
1181
+ new RegExp(`(?:const|let|var)\\s+${escapeRegex(returnVar)}\\s*(?::\\s*([^=]+?))?\\s*=\\s*(.+?)(?:;|$)`, "m")
1182
+ );
1183
+ if (declMatch) {
1184
+ const typeAnnotation = declMatch[1]?.trim();
1185
+ if (typeAnnotation) return typeAnnotation;
1186
+ const initializer = declMatch[2]?.trim();
1187
+ if (initializer) {
1188
+ if (/^\[/.test(initializer)) return "unknown[]";
1189
+ if (/^\{/.test(initializer)) return "Record<string, unknown>";
1190
+ if (/^["'`]/.test(initializer)) return "string";
1191
+ if (/^\d/.test(initializer)) return "number";
1192
+ if (/^(true|false)$/.test(initializer)) return "boolean";
1193
+ if (/^new Map/.test(initializer)) return "Map<unknown, unknown>";
1194
+ if (/^new Set/.test(initializer)) return "Set<unknown>";
1195
+ if (/\.map\s*\(/.test(initializer)) return "unknown[]";
1196
+ if (/\.filter\s*\(/.test(initializer)) return "unknown[]";
1197
+ }
1198
+ }
1199
+ return "unknown";
1200
+ }
1201
+ function escapeRegex(s) {
1202
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1203
+ }
1204
+ var httpWebhookPlugin = {
1205
+ nodeType: "http_webhook" /* HTTP_WEBHOOK */,
1206
+ generate: () => {
1207
+ },
1208
+ getOutputType(node) {
1209
+ const params = node.params;
1210
+ if (["GET", "DELETE"].includes(params.method)) {
1211
+ return "{ query: Record<string, string>; url: string }";
1212
+ }
1213
+ if (params.parseBody) {
1214
+ return "{ body: unknown; url: string }";
1215
+ }
1216
+ return "{ url: string }";
1217
+ }
1218
+ };
1219
+ var cronJobPlugin = {
1220
+ nodeType: "cron_job" /* CRON_JOB */,
1221
+ generate: () => {
1222
+ },
1223
+ getOutputType: () => "{ triggeredAt: string }"
1224
+ };
1225
+ var manualPlugin = {
1226
+ nodeType: "manual" /* MANUAL */,
1227
+ generate: () => {
1228
+ },
1229
+ getOutputType: () => "Record<string, unknown>"
1230
+ };
1231
+ var fetchApiPlugin = {
1232
+ nodeType: "fetch_api" /* FETCH_API */,
1233
+ generate(node, writer, context) {
1234
+ const params = node.params;
1235
+ const url = context.resolveEnvVars(params.url);
1236
+ writer.write("try ").block(() => {
1237
+ const hasBody = params.body && ["POST", "PUT", "PATCH"].includes(params.method);
1238
+ writer.writeLine(`const response = await fetch(${url}, {`);
1239
+ writer.writeLine(` method: "${params.method}",`);
1240
+ if (params.headers && Object.keys(params.headers).length > 0) {
1241
+ writer.writeLine(` headers: ${JSON.stringify(params.headers)},`);
1242
+ } else if (hasBody) {
1243
+ writer.writeLine(
1244
+ ` headers: { "Content-Type": "application/json" },`
1245
+ );
1246
+ }
1247
+ if (hasBody) {
1248
+ const bodyExpr = context.resolveExpression(params.body, node.id);
1249
+ if (bodyExpr.trimStart().startsWith("JSON.stringify")) {
1250
+ writer.writeLine(` body: ${bodyExpr},`);
1251
+ } else {
1252
+ writer.writeLine(` body: JSON.stringify(${bodyExpr}),`);
1253
+ }
1254
+ }
1255
+ writer.writeLine("});");
1256
+ writer.blankLine();
1257
+ writer.write("if (!response.ok) ").block(() => {
1258
+ writer.writeLine(
1259
+ `throw new Error(\`${node.label} failed: HTTP \${response.status} \${response.statusText}\`);`
1260
+ );
1261
+ });
1262
+ writer.blankLine();
1263
+ if (params.parseJson) {
1264
+ writer.writeLine(`const data = await response.json();`);
1265
+ writer.writeLine(`flowState['${node.id}'] = {`);
1266
+ writer.writeLine(` data,`);
1267
+ writer.writeLine(` status: response.status,`);
1268
+ writer.writeLine(` headers: Object.fromEntries(response.headers.entries()),`);
1269
+ writer.writeLine(`};`);
1270
+ } else {
1271
+ writer.writeLine(`flowState['${node.id}'] = response;`);
1272
+ }
1273
+ });
1274
+ writer.write(" catch (fetchError) ").block(() => {
1275
+ writer.writeLine(
1276
+ `console.error("Fetch failed for ${node.label}:", fetchError);`
1277
+ );
1278
+ writer.writeLine(`throw fetchError;`);
1279
+ });
1280
+ },
1281
+ getRequiredPackages: () => [],
1282
+ getOutputType(node) {
1283
+ const params = node.params;
1284
+ return params.parseJson ? "{ data: unknown; status: number; headers: Record<string, string> }" : "Response";
1285
+ }
1286
+ };
1287
+ var sqlQueryPlugin = {
1288
+ nodeType: "sql_query" /* SQL_QUERY */,
1289
+ generate(node, writer) {
1290
+ const params = node.params;
1291
+ switch (params.orm) {
1292
+ case "drizzle":
1293
+ writer.writeLine(`// Drizzle ORM Query`);
1294
+ writer.writeLine(
1295
+ `const result = await db.execute(sql\`${params.query}\`);`
1296
+ );
1297
+ writer.writeLine(`flowState['${node.id}'] = result;`);
1298
+ break;
1299
+ case "prisma":
1300
+ writer.writeLine(`// Prisma Query`);
1301
+ writer.writeLine(
1302
+ `const result = await prisma.$queryRaw\`${params.query}\`;`
1303
+ );
1304
+ writer.writeLine(`flowState['${node.id}'] = result;`);
1305
+ break;
1306
+ case "raw":
1307
+ default:
1308
+ writer.writeLine(`// Raw SQL Query`);
1309
+ writer.writeLine(
1310
+ `const result = await db.query(\`${params.query}\`);`
1311
+ );
1312
+ writer.writeLine(`flowState['${node.id}'] = result;`);
1313
+ break;
1314
+ }
1315
+ },
1316
+ getRequiredPackages(node) {
1317
+ const params = node.params;
1318
+ const map = {
1319
+ drizzle: ["drizzle-orm"],
1320
+ prisma: ["@prisma/client"],
1321
+ raw: []
1322
+ };
1323
+ return map[params.orm] ?? [];
1324
+ },
1325
+ getOutputType: () => "unknown[]"
1326
+ };
1327
+ var redisCachePlugin = {
1328
+ nodeType: "redis_cache" /* REDIS_CACHE */,
1329
+ generate(node, writer) {
1330
+ const params = node.params;
1331
+ const needsInterpolation = params.key.includes("${") || params.key.includes("{{");
1332
+ const keyExpr = needsInterpolation ? `\`${params.key}\`` : `"${params.key}"`;
1333
+ switch (params.operation) {
1334
+ case "get":
1335
+ writer.writeLine(
1336
+ `flowState['${node.id}'] = await redis.get(${keyExpr});`
1337
+ );
1338
+ break;
1339
+ case "set":
1340
+ if (params.ttl) {
1341
+ writer.writeLine(
1342
+ `await redis.set(${keyExpr}, ${params.value ?? "null"}, "EX", ${params.ttl});`
1343
+ );
1344
+ } else {
1345
+ writer.writeLine(
1346
+ `await redis.set(${keyExpr}, ${params.value ?? "null"});`
1347
+ );
1348
+ }
1349
+ writer.writeLine(`flowState['${node.id}'] = true;`);
1350
+ break;
1351
+ case "del":
1352
+ writer.writeLine(`await redis.del(${keyExpr});`);
1353
+ writer.writeLine(`flowState['${node.id}'] = true;`);
1354
+ break;
1355
+ }
1356
+ },
1357
+ getRequiredPackages: () => ["ioredis"],
1358
+ getOutputType(node) {
1359
+ const params = node.params;
1360
+ return params.operation === "get" ? "string | null" : "boolean";
1361
+ }
1362
+ };
1363
+ var DANGEROUS_CODE_PATTERNS = [
1364
+ { pattern: /\bprocess\.exit\b/, desc: "process.exit() \u2014 terminates the Node.js process" },
1365
+ { pattern: /\bchild_process\b/, desc: "child_process \u2014 can execute arbitrary system commands" },
1366
+ { pattern: /\beval\s*\(/, desc: "eval() \u2014 dynamically executes arbitrary code" },
1367
+ { pattern: /\bnew\s+Function\s*\(/, desc: "new Function() \u2014 dynamically constructs functions" },
1368
+ { pattern: /\brequire\s*\(\s*['"]fs['"]/, desc: "require('fs') \u2014 file system access" },
1369
+ { pattern: /\bimport\s*\(\s*['"]fs['"]/, desc: "import('fs') \u2014 file system access" },
1370
+ { pattern: /\bfs\.\w*(unlink|rmdir|rm|writeFile)\b/, desc: "fs delete/write operations" }
1371
+ ];
1372
+ var customCodePlugin = {
1373
+ nodeType: "custom_code" /* CUSTOM_CODE */,
1374
+ generate(node, writer, context) {
1375
+ const params = node.params;
1376
+ const warnings = [];
1377
+ for (const { pattern, desc } of DANGEROUS_CODE_PATTERNS) {
1378
+ if (pattern.test(params.code)) {
1379
+ warnings.push(desc);
1380
+ }
1381
+ }
1382
+ if (warnings.length > 0) {
1383
+ writer.writeLine(`// \u26A0\uFE0F SECURITY WARNING: This custom code uses the following dangerous APIs:`);
1384
+ for (const w of warnings) {
1385
+ writer.writeLine(`// - ${w}`);
1386
+ }
1387
+ writer.writeLine(`// Please carefully review this code before deployment.`);
1388
+ if (context && "addWarning" in context) {
1389
+ const addWarning = context.addWarning;
1390
+ addWarning?.(`[${node.id}] Custom code uses dangerous API: ${warnings.join(", ")}`);
1391
+ }
1392
+ }
1393
+ const resultVar = `custom_result_${node.id.replace(/[^a-zA-Z0-9_]/g, "_")}`;
1394
+ if (params.returnVariable) {
1395
+ writer.writeLine(`const ${resultVar} = await (async () => {`);
1396
+ } else {
1397
+ writer.writeLine(`await (async () => {`);
1398
+ }
1399
+ writer.writeLine(`// Custom Code: ${node.label}`);
1400
+ const lines = params.code.split("\n");
1401
+ for (const line of lines) {
1402
+ writer.writeLine(` ${line}`);
1403
+ }
1404
+ if (params.returnVariable) {
1405
+ writer.writeLine(` if (typeof ${params.returnVariable} !== 'undefined') return ${params.returnVariable};`);
1406
+ }
1407
+ writer.writeLine(`})();`);
1408
+ if (params.returnVariable) {
1409
+ writer.writeLine(`flowState['${node.id}'] = ${resultVar};`);
1410
+ }
1411
+ },
1412
+ getOutputType(node) {
1413
+ const params = node.params;
1414
+ if (params.returnType) return params.returnType;
1415
+ if (!params.returnVariable) return "void";
1416
+ return inferTypeFromCode(params.code, params.returnVariable);
1417
+ }
1418
+ };
1419
+ var callSubflowPlugin = {
1420
+ nodeType: "call_subflow" /* CALL_SUBFLOW */,
1421
+ generate(node, writer, context) {
1422
+ const params = node.params;
1423
+ const existing = context.imports.get(params.flowPath);
1424
+ if (existing) {
1425
+ existing.add(params.functionName);
1426
+ } else {
1427
+ context.imports.set(params.flowPath, /* @__PURE__ */ new Set([params.functionName]));
1428
+ }
1429
+ const args = Object.entries(params.inputMapping).map(([key, expr]) => {
1430
+ const resolved = context.resolveExpression(expr, node.id);
1431
+ return `${key}: ${resolved}`;
1432
+ }).join(", ");
1433
+ writer.writeLine(
1434
+ `flowState['${node.id}'] = await ${params.functionName}({ ${args} });`
1435
+ );
1436
+ },
1437
+ getOutputType(node) {
1438
+ const params = node.params;
1439
+ if (params.returnType) return params.returnType;
1440
+ return `Awaited<ReturnType<typeof ${params.functionName}>>`;
1441
+ }
1442
+ };
1443
+ var ifElsePlugin = {
1444
+ nodeType: "if_else" /* IF_ELSE */,
1445
+ generate(node, writer, context) {
1446
+ const params = node.params;
1447
+ const trueEdges = context.ir.edges.filter(
1448
+ (e) => e.sourceNodeId === node.id && e.sourcePortId === "true"
1449
+ );
1450
+ const falseEdges = context.ir.edges.filter(
1451
+ (e) => e.sourceNodeId === node.id && e.sourcePortId === "false"
1452
+ );
1453
+ const conditionExpr = context.resolveExpression(
1454
+ params.condition,
1455
+ node.id
1456
+ );
1457
+ writer.write(`if (${conditionExpr}) `).block(() => {
1458
+ writer.writeLine(`flowState['${node.id}'] = true;`);
1459
+ for (const edge of trueEdges) {
1460
+ const childNode = context.nodeMap.get(edge.targetNodeId);
1461
+ if (childNode) {
1462
+ context.generateChildNode(writer, childNode);
1463
+ }
1464
+ }
1465
+ });
1466
+ if (falseEdges.length > 0) {
1467
+ writer.write(" else ").block(() => {
1468
+ writer.writeLine(`flowState['${node.id}'] = false;`);
1469
+ for (const edge of falseEdges) {
1470
+ const childNode = context.nodeMap.get(edge.targetNodeId);
1471
+ if (childNode) {
1472
+ context.generateChildNode(writer, childNode);
1473
+ }
1474
+ }
1475
+ });
1476
+ }
1477
+ },
1478
+ getOutputType: () => "boolean"
1479
+ };
1480
+ var forLoopPlugin = {
1481
+ nodeType: "for_loop" /* FOR_LOOP */,
1482
+ generate(node, writer, context) {
1483
+ const params = node.params;
1484
+ const iterableExpr = context.resolveExpression(
1485
+ params.iterableExpression,
1486
+ node.id
1487
+ );
1488
+ const sanitizedId = node.id.replace(/[^a-zA-Z0-9_]/g, "_");
1489
+ const scopeVar = `_scope_${sanitizedId}`;
1490
+ writer.writeLine(`const ${sanitizedId}_results: unknown[] = [];`);
1491
+ if (params.indexVariable) {
1492
+ writer.write(
1493
+ `for (const [${params.indexVariable}, ${params.itemVariable}] of (${iterableExpr}).entries()) `
1494
+ );
1495
+ } else {
1496
+ writer.write(
1497
+ `for (const ${params.itemVariable} of ${iterableExpr}) `
1498
+ );
1499
+ }
1500
+ writer.block(() => {
1501
+ writer.writeLine(
1502
+ `const ${scopeVar}: Record<string, unknown> = {};`
1503
+ );
1504
+ writer.writeLine(
1505
+ `${scopeVar}['${node.id}'] = ${params.itemVariable};`
1506
+ );
1507
+ context.pushScope(node.id, scopeVar);
1508
+ const childEdges = context.ir.edges.filter(
1509
+ (e) => e.sourceNodeId === node.id && e.sourcePortId === "body"
1510
+ );
1511
+ for (const edge of childEdges) {
1512
+ const childNode = context.nodeMap.get(edge.targetNodeId);
1513
+ if (childNode) {
1514
+ context.generateChildNode(writer, childNode);
1515
+ }
1516
+ }
1517
+ context.popScope();
1518
+ writer.writeLine(`${sanitizedId}_results.push(${params.itemVariable});`);
1519
+ });
1520
+ writer.writeLine(`flowState['${node.id}'] = ${sanitizedId}_results;`);
1521
+ },
1522
+ getOutputType: () => "unknown[]"
1523
+ };
1524
+ var tryCatchPlugin = {
1525
+ nodeType: "try_catch" /* TRY_CATCH */,
1526
+ generate(node, writer, context) {
1527
+ const params = node.params;
1528
+ const successEdges = context.ir.edges.filter(
1529
+ (e) => e.sourceNodeId === node.id && e.sourcePortId === "success"
1530
+ );
1531
+ const errorEdges = context.ir.edges.filter(
1532
+ (e) => e.sourceNodeId === node.id && e.sourcePortId === "error"
1533
+ );
1534
+ const tryScopeVar = `_scope_${node.id.replace(/[^a-zA-Z0-9_]/g, "_")}_try`;
1535
+ const catchScopeVar = `_scope_${node.id.replace(/[^a-zA-Z0-9_]/g, "_")}_catch`;
1536
+ writer.write("try ").block(() => {
1537
+ writer.writeLine(
1538
+ `const ${tryScopeVar}: Record<string, unknown> = {};`
1539
+ );
1540
+ context.pushScope(node.id, tryScopeVar);
1541
+ for (const edge of successEdges) {
1542
+ const childNode = context.nodeMap.get(edge.targetNodeId);
1543
+ if (childNode) {
1544
+ context.generateChildNode(writer, childNode);
1545
+ }
1546
+ }
1547
+ context.popScope();
1548
+ writer.writeLine(
1549
+ `flowState['${node.id}'] = { success: true };`
1550
+ );
1551
+ });
1552
+ writer.write(` catch (${params.errorVariable}) `).block(() => {
1553
+ writer.writeLine(
1554
+ `console.error("Error in ${node.label}:", ${params.errorVariable});`
1555
+ );
1556
+ writer.writeLine(
1557
+ `const ${catchScopeVar}: Record<string, unknown> = {};`
1558
+ );
1559
+ writer.writeLine(
1560
+ `flowState['${node.id}'] = { success: false, error: ${params.errorVariable} };`
1561
+ );
1562
+ context.pushScope(node.id, catchScopeVar);
1563
+ for (const edge of errorEdges) {
1564
+ const childNode = context.nodeMap.get(edge.targetNodeId);
1565
+ if (childNode) {
1566
+ context.generateChildNode(writer, childNode);
1567
+ }
1568
+ }
1569
+ context.popScope();
1570
+ });
1571
+ },
1572
+ getOutputType: () => "{ success: boolean; error?: unknown }"
1573
+ };
1574
+ var promiseAllPlugin = {
1575
+ nodeType: "promise_all" /* PROMISE_ALL */,
1576
+ generate(node, writer) {
1577
+ writer.writeLine(`// Promise.all handled by concurrent execution`);
1578
+ writer.writeLine(
1579
+ `flowState['${node.id}'] = undefined; // populated by concurrent handler`
1580
+ );
1581
+ },
1582
+ getOutputType: () => "unknown[]"
1583
+ };
1584
+ var declarePlugin = {
1585
+ nodeType: "declare" /* DECLARE */,
1586
+ generate(node, writer) {
1587
+ const params = node.params;
1588
+ const keyword = params.isConst ? "const" : "let";
1589
+ const initialValue = params.initialValue ?? "undefined";
1590
+ writer.writeLine(`${keyword} ${params.name} = ${initialValue};`);
1591
+ writer.writeLine(`flowState['${node.id}'] = ${params.name};`);
1592
+ },
1593
+ getOutputType(node) {
1594
+ const params = node.params;
1595
+ return params.dataType;
1596
+ }
1597
+ };
1598
+ var transformPlugin = {
1599
+ nodeType: "transform" /* TRANSFORM */,
1600
+ generate(node, writer, context) {
1601
+ const params = node.params;
1602
+ const expr = context.resolveExpression(params.expression, node.id);
1603
+ writer.writeLine(`flowState['${node.id}'] = ${expr};`);
1604
+ },
1605
+ getOutputType(node) {
1606
+ const params = node.params;
1607
+ return inferTypeFromExpression(params.expression);
1608
+ }
1609
+ };
1610
+ var returnResponsePlugin = {
1611
+ nodeType: "return_response" /* RETURN_RESPONSE */,
1612
+ generate(node, writer, context) {
1613
+ const params = node.params;
1614
+ const bodyExpr = context.resolveExpression(
1615
+ params.bodyExpression,
1616
+ node.id
1617
+ );
1618
+ const ctx = context;
1619
+ if (ctx.__platformResponse) {
1620
+ ctx.__platformResponse(writer, bodyExpr, params.statusCode, params.headers);
1621
+ } else {
1622
+ if (params.headers && Object.keys(params.headers).length > 0) {
1623
+ writer.writeLine(
1624
+ `return NextResponse.json(${bodyExpr}, { status: ${params.statusCode}, headers: ${JSON.stringify(params.headers)} });`
1625
+ );
1626
+ } else {
1627
+ writer.writeLine(
1628
+ `return NextResponse.json(${bodyExpr}, { status: ${params.statusCode} });`
1629
+ );
1630
+ }
1631
+ }
1632
+ },
1633
+ getOutputType: () => "never"
1634
+ };
1635
+ var builtinPlugins = [
1636
+ // Triggers
1637
+ httpWebhookPlugin,
1638
+ cronJobPlugin,
1639
+ manualPlugin,
1640
+ // Actions
1641
+ fetchApiPlugin,
1642
+ sqlQueryPlugin,
1643
+ redisCachePlugin,
1644
+ customCodePlugin,
1645
+ callSubflowPlugin,
1646
+ // Logic
1647
+ ifElsePlugin,
1648
+ forLoopPlugin,
1649
+ tryCatchPlugin,
1650
+ promiseAllPlugin,
1651
+ // Variables
1652
+ declarePlugin,
1653
+ transformPlugin,
1654
+ // Output
1655
+ returnResponsePlugin
1656
+ ];
1657
+
1658
+ // src/lib/compiler/type-inference.ts
1659
+ function inferFlowStateTypes(ir, registry) {
1660
+ const nodeTypes = /* @__PURE__ */ new Map();
1661
+ for (const node of ir.nodes) {
1662
+ const type = inferNodeOutputType(node, registry);
1663
+ nodeTypes.set(node.id, type);
1664
+ }
1665
+ const fields = ir.nodes.map((node) => {
1666
+ const type = nodeTypes.get(node.id) || "unknown";
1667
+ const safeId = node.id;
1668
+ return ` '${safeId}'?: ${type};`;
1669
+ }).join("\n");
1670
+ const interfaceCode = `interface FlowState {
1671
+ ${fields}
1672
+ }`;
1673
+ return { interfaceCode, nodeTypes };
1674
+ }
1675
+ function inferNodeOutputType(node, registry) {
1676
+ const plugin = registry?.get(node.nodeType) ?? getPlugin(node.nodeType);
1677
+ if (plugin?.getOutputType) {
1678
+ return plugin.getOutputType(node);
1679
+ }
1680
+ if (node.outputs && node.outputs.length > 0) {
1681
+ const primaryOutput = node.outputs[0];
1682
+ return mapFlowDataTypeToTS(primaryOutput.dataType);
1683
+ }
1684
+ return "unknown";
1685
+ }
1686
+ function mapFlowDataTypeToTS(dataType) {
1687
+ switch (dataType) {
1688
+ case "string":
1689
+ return "string";
1690
+ case "number":
1691
+ return "number";
1692
+ case "boolean":
1693
+ return "boolean";
1694
+ case "object":
1695
+ return "Record<string, unknown>";
1696
+ case "array":
1697
+ return "unknown[]";
1698
+ case "void":
1699
+ return "void";
1700
+ case "Response":
1701
+ return "Response";
1702
+ case "any":
1703
+ default:
1704
+ return "unknown";
1705
+ }
1706
+ }
1707
+
1708
+ // src/lib/compiler/symbol-table.ts
1709
+ function buildSymbolTable(ir) {
1710
+ const mappings = /* @__PURE__ */ new Map();
1711
+ const usedNames = /* @__PURE__ */ new Set();
1712
+ for (const node of ir.nodes) {
1713
+ let name = labelToVarName(node.label);
1714
+ if (!name || RESERVED_WORDS.has(name)) {
1715
+ name = `${name || "node"}Result`;
1716
+ }
1717
+ if (/^[0-9]/.test(name)) {
1718
+ name = `_${name}`;
1719
+ }
1720
+ let uniqueName = name;
1721
+ let counter = 2;
1722
+ while (usedNames.has(uniqueName)) {
1723
+ uniqueName = `${name}${counter}`;
1724
+ counter++;
1725
+ }
1726
+ usedNames.add(uniqueName);
1727
+ mappings.set(node.id, uniqueName);
1728
+ }
1729
+ return {
1730
+ getVarName(nodeId) {
1731
+ return mappings.get(nodeId) ?? `node_${nodeId.replace(/[^a-zA-Z0-9_]/g, "_")}`;
1732
+ },
1733
+ hasVar(nodeId) {
1734
+ return mappings.has(nodeId);
1735
+ },
1736
+ getAllMappings() {
1737
+ return mappings;
1738
+ }
1739
+ };
1740
+ }
1741
+ function labelToVarName(label) {
1742
+ const cleaned = label.replace(/[/\-_.]/g, " ").replace(/[^a-zA-Z0-9\s]/g, "").trim();
1743
+ if (!cleaned) return "";
1744
+ const words = cleaned.replace(/([a-z])([A-Z])/g, "$1 $2").split(/\s+/).filter((w) => w.length > 0);
1745
+ if (words.length === 0) return "";
1746
+ return words.map((word, i) => {
1747
+ const lower = word.toLowerCase();
1748
+ if (i === 0) return lower;
1749
+ return lower.charAt(0).toUpperCase() + lower.slice(1);
1750
+ }).join("");
1751
+ }
1752
+ var RESERVED_WORDS = /* @__PURE__ */ new Set([
1753
+ // JavaScript reserved words
1754
+ "break",
1755
+ "case",
1756
+ "catch",
1757
+ "continue",
1758
+ "debugger",
1759
+ "default",
1760
+ "delete",
1761
+ "do",
1762
+ "else",
1763
+ "finally",
1764
+ "for",
1765
+ "function",
1766
+ "if",
1767
+ "in",
1768
+ "instanceof",
1769
+ "new",
1770
+ "return",
1771
+ "switch",
1772
+ "this",
1773
+ "throw",
1774
+ "try",
1775
+ "typeof",
1776
+ "var",
1777
+ "void",
1778
+ "while",
1779
+ "with",
1780
+ "class",
1781
+ "const",
1782
+ "enum",
1783
+ "export",
1784
+ "extends",
1785
+ "import",
1786
+ "super",
1787
+ "implements",
1788
+ "interface",
1789
+ "let",
1790
+ "package",
1791
+ "private",
1792
+ "protected",
1793
+ "public",
1794
+ "static",
1795
+ "yield",
1796
+ "await",
1797
+ "async",
1798
+ // Built-in global values
1799
+ "undefined",
1800
+ "null",
1801
+ "true",
1802
+ "false",
1803
+ "NaN",
1804
+ "Infinity",
1805
+ // Common identifiers in generated code (avoid shadowing)
1806
+ "req",
1807
+ "res",
1808
+ "body",
1809
+ "query",
1810
+ "data",
1811
+ "result",
1812
+ "response",
1813
+ "error",
1814
+ "flowState",
1815
+ "searchParams",
1816
+ "NextResponse",
1817
+ "NextRequest"
1818
+ ]);
1819
+
1820
+ // src/lib/compiler/compiler.ts
1821
+ function compile(ir, options) {
1822
+ const pluginRegistry = createPluginRegistry();
1823
+ pluginRegistry.registerAll(builtinPlugins);
1824
+ if (options?.plugins) {
1825
+ pluginRegistry.registerAll(options.plugins);
1826
+ }
1827
+ const validation = validateFlowIR(ir);
1828
+ if (!validation.valid) {
1829
+ return {
1830
+ success: false,
1831
+ errors: validation.errors.map((e) => `[${e.code}] ${e.message}`)
1832
+ };
1833
+ }
1834
+ let plan;
1835
+ try {
1836
+ plan = topologicalSort(ir);
1837
+ } catch (err) {
1838
+ return {
1839
+ success: false,
1840
+ errors: [err instanceof Error ? err.message : String(err)]
1841
+ };
1842
+ }
1843
+ const nodeMap = new Map(ir.nodes.map((n) => [n.id, n]));
1844
+ const platformName = options?.platform ?? "nextjs";
1845
+ const platform = getPlatform(platformName);
1846
+ const symbolTable = buildSymbolTable(ir);
1847
+ const context = {
1848
+ ir,
1849
+ plan,
1850
+ nodeMap,
1851
+ envVars: /* @__PURE__ */ new Set(),
1852
+ imports: /* @__PURE__ */ new Map(),
1853
+ requiredPackages: /* @__PURE__ */ new Set(),
1854
+ sourceMapEntries: /* @__PURE__ */ new Map(),
1855
+ currentLine: 1,
1856
+ platform,
1857
+ symbolTable,
1858
+ scopeStack: [],
1859
+ childBlockNodeIds: /* @__PURE__ */ new Set(),
1860
+ dagMode: false,
1861
+ symbolTableExclusions: /* @__PURE__ */ new Set(),
1862
+ generatedBlockNodeIds: /* @__PURE__ */ new Set(),
1863
+ pluginRegistry
1864
+ };
1865
+ const trigger = ir.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
1866
+ const preComputedBlockNodes = computeControlFlowDescendants(ir, trigger.id);
1867
+ for (const nodeId of preComputedBlockNodes) {
1868
+ context.childBlockNodeIds.add(nodeId);
1869
+ context.symbolTableExclusions.add(nodeId);
1870
+ }
1871
+ const hasConcurrency = plan.steps.some(
1872
+ (s) => s.concurrent && s.nodeIds.filter((id) => id !== trigger.id).length > 1
1873
+ );
1874
+ if (hasConcurrency) {
1875
+ context.dagMode = true;
1876
+ for (const node of ir.nodes) {
1877
+ if (node.category !== "trigger" /* TRIGGER */) {
1878
+ context.symbolTableExclusions.add(node.id);
1879
+ }
1880
+ }
1881
+ }
1882
+ const project = new import_ts_morph.Project({ useInMemoryFileSystem: true });
1883
+ const sourceFile = project.createSourceFile("generated.ts", "");
1884
+ try {
1885
+ generateCode(sourceFile, trigger, context);
1886
+ } catch (err) {
1887
+ throw err;
1888
+ }
1889
+ sourceFile.formatText({
1890
+ indentSize: 2,
1891
+ convertTabsToSpaces: true
1892
+ });
1893
+ const code = sourceFile.getFullText();
1894
+ const filePath = platform.getOutputFilePath(trigger);
1895
+ collectRequiredPackages(ir, context);
1896
+ const sourceMap = buildSourceMap(code, ir, filePath);
1897
+ const dependencies = {
1898
+ all: [...context.requiredPackages].sort(),
1899
+ missing: [...context.requiredPackages].sort(),
1900
+ installCommand: context.requiredPackages.size > 0 ? `npm install ${[...context.requiredPackages].sort().join(" ")}` : void 0
1901
+ };
1902
+ return {
1903
+ success: true,
1904
+ code,
1905
+ filePath,
1906
+ dependencies,
1907
+ sourceMap
1908
+ };
1909
+ }
1910
+ function generateCode(sourceFile, trigger, context) {
1911
+ const { platform } = context;
1912
+ platform.generateImports(sourceFile, trigger, {
1913
+ ir: context.ir,
1914
+ nodeMap: context.nodeMap,
1915
+ envVars: context.envVars,
1916
+ imports: context.imports
1917
+ });
1918
+ platform.generateFunction(
1919
+ sourceFile,
1920
+ trigger,
1921
+ {
1922
+ ir: context.ir,
1923
+ nodeMap: context.nodeMap,
1924
+ envVars: context.envVars,
1925
+ imports: context.imports
1926
+ },
1927
+ (writer) => {
1928
+ generateFunctionBody(writer, trigger, context);
1929
+ }
1930
+ );
1931
+ }
1932
+ function generateFunctionBody(writer, trigger, context) {
1933
+ const { ir } = context;
1934
+ const typeInfo = inferFlowStateTypes(ir, context.pluginRegistry);
1935
+ writer.writeLine(typeInfo.interfaceCode);
1936
+ writer.writeLine("const flowState: Partial<FlowState> = {};");
1937
+ writer.blankLine();
1938
+ context.platform.generateTriggerInit(writer, trigger, {
1939
+ symbolTable: context.symbolTable
1940
+ });
1941
+ writer.blankLine();
1942
+ generateNodeChain(writer, trigger.id, context);
1943
+ }
1944
+ var CONTROL_FLOW_PORT_MAP = {
1945
+ ["if_else" /* IF_ELSE */]: /* @__PURE__ */ new Set(["true", "false"]),
1946
+ ["for_loop" /* FOR_LOOP */]: /* @__PURE__ */ new Set(["body"]),
1947
+ ["try_catch" /* TRY_CATCH */]: /* @__PURE__ */ new Set(["success", "error"])
1948
+ };
1949
+ function isControlFlowEdge(edge, nodeMap) {
1950
+ const sourceNode = nodeMap.get(edge.sourceNodeId);
1951
+ if (!sourceNode) return false;
1952
+ const controlPorts = CONTROL_FLOW_PORT_MAP[sourceNode.nodeType];
1953
+ return controlPorts !== void 0 && controlPorts.has(edge.sourcePortId);
1954
+ }
1955
+ function computeControlFlowDescendants(ir, triggerId) {
1956
+ const nodeMap = new Map(ir.nodes.map((n) => [n.id, n]));
1957
+ const strippedSuccessors = /* @__PURE__ */ new Map();
1958
+ for (const node of ir.nodes) {
1959
+ strippedSuccessors.set(node.id, /* @__PURE__ */ new Set());
1960
+ }
1961
+ for (const edge of ir.edges) {
1962
+ if (isControlFlowEdge(edge, nodeMap)) continue;
1963
+ strippedSuccessors.get(edge.sourceNodeId)?.add(edge.targetNodeId);
1964
+ }
1965
+ const reachableWithoutControlFlow = /* @__PURE__ */ new Set();
1966
+ const queue = [triggerId];
1967
+ while (queue.length > 0) {
1968
+ const id = queue.shift();
1969
+ if (reachableWithoutControlFlow.has(id)) continue;
1970
+ reachableWithoutControlFlow.add(id);
1971
+ for (const succ of strippedSuccessors.get(id) ?? []) {
1972
+ if (!reachableWithoutControlFlow.has(succ)) {
1973
+ queue.push(succ);
1974
+ }
1975
+ }
1976
+ }
1977
+ const childBlockNodeIds = /* @__PURE__ */ new Set();
1978
+ for (const node of ir.nodes) {
1979
+ if (node.id === triggerId) continue;
1980
+ if (!reachableWithoutControlFlow.has(node.id)) {
1981
+ childBlockNodeIds.add(node.id);
1982
+ }
1983
+ }
1984
+ return childBlockNodeIds;
1985
+ }
1986
+ function generateBlockContinuation(writer, fromNodeId, context) {
1987
+ const reachable = /* @__PURE__ */ new Set();
1988
+ const bfsQueue = [fromNodeId];
1989
+ const edgeSuccessors = /* @__PURE__ */ new Map();
1990
+ for (const edge of context.ir.edges) {
1991
+ if (!edgeSuccessors.has(edge.sourceNodeId)) {
1992
+ edgeSuccessors.set(edge.sourceNodeId, []);
1993
+ }
1994
+ edgeSuccessors.get(edge.sourceNodeId).push(edge.targetNodeId);
1995
+ }
1996
+ while (bfsQueue.length > 0) {
1997
+ const id = bfsQueue.shift();
1998
+ if (reachable.has(id)) continue;
1999
+ reachable.add(id);
2000
+ for (const succ of edgeSuccessors.get(id) ?? []) {
2001
+ if (!reachable.has(succ)) {
2002
+ bfsQueue.push(succ);
2003
+ }
2004
+ }
2005
+ }
2006
+ for (const nodeId of context.plan.sortedNodeIds) {
2007
+ if (nodeId === fromNodeId) continue;
2008
+ if (!reachable.has(nodeId)) continue;
2009
+ if (!context.childBlockNodeIds.has(nodeId)) continue;
2010
+ if (context.generatedBlockNodeIds.has(nodeId)) continue;
2011
+ const deps = context.plan.dependencies.get(nodeId) ?? /* @__PURE__ */ new Set();
2012
+ const allBlockDepsReady = [...deps].every((depId) => {
2013
+ if (!context.childBlockNodeIds.has(depId)) return true;
2014
+ return context.generatedBlockNodeIds.has(depId);
2015
+ });
2016
+ if (!allBlockDepsReady) continue;
2017
+ const node = context.nodeMap.get(nodeId);
2018
+ if (!node) continue;
2019
+ context.generatedBlockNodeIds.add(nodeId);
2020
+ context.symbolTableExclusions.add(nodeId);
2021
+ writer.writeLine(`// --- ${node.label} (${node.nodeType}) [${node.id}] ---`);
2022
+ generateNodeBody(writer, node, context);
2023
+ }
2024
+ }
2025
+ function generateNodeChain(writer, triggerId, context) {
2026
+ if (context.dagMode) {
2027
+ generateNodeChainDAG(writer, triggerId, context);
2028
+ } else {
2029
+ generateNodeChainSequential(writer, triggerId, context);
2030
+ }
2031
+ }
2032
+ function generateNodeChainSequential(writer, triggerId, context) {
2033
+ const { plan, nodeMap } = context;
2034
+ for (const step of plan.steps) {
2035
+ const activeNodes = step.nodeIds.filter(
2036
+ (id) => id !== triggerId && !context.childBlockNodeIds.has(id)
2037
+ );
2038
+ if (activeNodes.length === 0) continue;
2039
+ if (step.concurrent && activeNodes.length > 1) {
2040
+ generateConcurrentNodes(writer, activeNodes, context);
2041
+ } else {
2042
+ for (const nodeId of activeNodes) {
2043
+ const node = nodeMap.get(nodeId);
2044
+ if (!node) continue;
2045
+ generateSingleNode(writer, node, context);
2046
+ writer.blankLine();
2047
+ }
2048
+ }
2049
+ }
2050
+ }
2051
+ function resolveToDAGNodes(depId, triggerId, context, visited = /* @__PURE__ */ new Set()) {
2052
+ if (visited.has(depId) || depId === triggerId) return [];
2053
+ visited.add(depId);
2054
+ if (!context.childBlockNodeIds.has(depId)) return [depId];
2055
+ const parentDeps = context.plan.dependencies.get(depId) ?? /* @__PURE__ */ new Set();
2056
+ const result = [];
2057
+ for (const pd of parentDeps) {
2058
+ result.push(...resolveToDAGNodes(pd, triggerId, context, visited));
2059
+ }
2060
+ return result;
2061
+ }
2062
+ function generateNodeChainDAG(writer, triggerId, context) {
2063
+ const { plan, nodeMap } = context;
2064
+ const allNodeIds = plan.sortedNodeIds.filter((id) => id !== triggerId);
2065
+ const outputNodeIds = [];
2066
+ const dagNodeIds = [];
2067
+ for (const id of allNodeIds) {
2068
+ if (context.childBlockNodeIds.has(id)) continue;
2069
+ const node = nodeMap.get(id);
2070
+ if (!node) continue;
2071
+ if (node.category === "output" /* OUTPUT */) {
2072
+ outputNodeIds.push(id);
2073
+ } else {
2074
+ dagNodeIds.push(id);
2075
+ }
2076
+ }
2077
+ if (dagNodeIds.length > 0) {
2078
+ writer.writeLine("// --- DAG Concurrent Execution ---");
2079
+ writer.blankLine();
2080
+ }
2081
+ for (const nodeId of dagNodeIds) {
2082
+ const node = nodeMap.get(nodeId);
2083
+ const rawDeps = [...plan.dependencies.get(nodeId) ?? []];
2084
+ const resolvedDeps = /* @__PURE__ */ new Set();
2085
+ for (const depId of rawDeps) {
2086
+ for (const dagDep of resolveToDAGNodes(depId, triggerId, context)) {
2087
+ resolvedDeps.add(dagDep);
2088
+ }
2089
+ }
2090
+ resolvedDeps.delete(nodeId);
2091
+ const promiseVar = `p_${sanitizeId(nodeId)}`;
2092
+ writer.write(`const ${promiseVar} = (async () => `).block(() => {
2093
+ for (const depId of resolvedDeps) {
2094
+ writer.writeLine(`await p_${sanitizeId(depId)};`);
2095
+ }
2096
+ writer.writeLine(`// --- ${node.label} (${node.nodeType}) [${node.id}] ---`);
2097
+ generateNodeBody(writer, node, context);
2098
+ });
2099
+ writer.writeLine(`)();`);
2100
+ writer.writeLine(`${promiseVar}.catch(() => {});`);
2101
+ writer.blankLine();
2102
+ }
2103
+ if (dagNodeIds.length > 0) {
2104
+ writer.writeLine("// --- Sync Barrier: await all DAG promises before output ---");
2105
+ const allPromiseVars = dagNodeIds.map((id) => `p_${sanitizeId(id)}`);
2106
+ writer.writeLine(`await Promise.allSettled([${allPromiseVars.join(", ")}]);`);
2107
+ writer.blankLine();
2108
+ }
2109
+ for (const nodeId of outputNodeIds) {
2110
+ const node = nodeMap.get(nodeId);
2111
+ const rawDeps = [...plan.dependencies.get(nodeId) ?? []];
2112
+ const resolvedDeps = /* @__PURE__ */ new Set();
2113
+ for (const depId of rawDeps) {
2114
+ for (const dagDep of resolveToDAGNodes(depId, triggerId, context)) {
2115
+ resolvedDeps.add(dagDep);
2116
+ }
2117
+ }
2118
+ for (const depId of resolvedDeps) {
2119
+ writer.writeLine(`await p_${sanitizeId(depId)};`);
2120
+ }
2121
+ writer.writeLine(`// --- ${node.label} (${node.nodeType}) [${node.id}] ---`);
2122
+ generateNodeBody(writer, node, context);
2123
+ }
2124
+ }
2125
+ function generateConcurrentNodes(writer, nodeIds, context) {
2126
+ const { nodeMap } = context;
2127
+ const activeNodeIds = nodeIds.filter((id) => !context.childBlockNodeIds.has(id));
2128
+ if (activeNodeIds.length === 0) return;
2129
+ if (activeNodeIds.length === 1) {
2130
+ const node = nodeMap.get(activeNodeIds[0]);
2131
+ if (node) {
2132
+ generateSingleNode(writer, node, context);
2133
+ writer.blankLine();
2134
+ }
2135
+ return;
2136
+ }
2137
+ writer.writeLine("// --- Concurrent Execution ---");
2138
+ const taskNames = [];
2139
+ for (const nodeId of activeNodeIds) {
2140
+ const node = nodeMap.get(nodeId);
2141
+ if (!node) continue;
2142
+ const taskName = `task_${sanitizeId(nodeId)}`;
2143
+ taskNames.push(taskName);
2144
+ writer.write(`const ${taskName} = async () => `).block(() => {
2145
+ generateNodeBody(writer, node, context);
2146
+ });
2147
+ writer.writeLine(";");
2148
+ }
2149
+ writer.writeLine(
2150
+ `const [${taskNames.map((_, i) => `r${i}`).join(", ")}] = await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
2151
+ );
2152
+ activeNodeIds.forEach((nodeId, i) => {
2153
+ writer.writeLine(`flowState['${nodeId}'] = r${i};`);
2154
+ });
2155
+ activeNodeIds.forEach((nodeId) => {
2156
+ const varName = context.symbolTable.getVarName(nodeId);
2157
+ writer.writeLine(`const ${varName} = flowState['${nodeId}'];`);
2158
+ });
2159
+ writer.blankLine();
2160
+ }
2161
+ function generateSingleNode(writer, node, context) {
2162
+ writer.writeLine(`// --- ${node.label} (${node.nodeType}) [${node.id}] ---`);
2163
+ generateNodeBody(writer, node, context);
2164
+ if (node.category !== "output" /* OUTPUT */) {
2165
+ const varName = context.symbolTable.getVarName(node.id);
2166
+ writer.writeLine(`const ${varName} = flowState['${node.id}'];`);
2167
+ }
2168
+ }
2169
+ function generateNodeBody(writer, node, context) {
2170
+ const plugin = context.pluginRegistry.get(node.nodeType);
2171
+ if (plugin) {
2172
+ const pluginCtx = createPluginContext(context);
2173
+ plugin.generate(node, writer, pluginCtx);
2174
+ } else {
2175
+ throw new Error(
2176
+ `[flow2code] Unsupported node type: "${node.nodeType}". Register a plugin via pluginRegistry.register() or use a built-in node type.`
2177
+ );
2178
+ }
2179
+ }
2180
+ function createPluginContext(context) {
2181
+ return {
2182
+ ir: context.ir,
2183
+ nodeMap: context.nodeMap,
2184
+ envVars: context.envVars,
2185
+ imports: context.imports,
2186
+ requiredPackages: context.requiredPackages,
2187
+ getVarName(nodeId) {
2188
+ return context.symbolTable.getVarName(nodeId);
2189
+ },
2190
+ resolveExpression(expr, currentNodeId) {
2191
+ const exprContext = {
2192
+ ir: context.ir,
2193
+ nodeMap: context.nodeMap,
2194
+ symbolTable: context.symbolTable,
2195
+ scopeStack: context.scopeStack.length > 0 ? [...context.scopeStack] : void 0,
2196
+ // Merge child block + DAG exclusion list
2197
+ blockScopedNodeIds: context.symbolTableExclusions.size > 0 ? context.symbolTableExclusions : void 0,
2198
+ currentNodeId
2199
+ };
2200
+ return parseExpression(expr, exprContext);
2201
+ },
2202
+ resolveEnvVars(url) {
2203
+ return resolveEnvVars(url, context);
2204
+ },
2205
+ generateChildNode(writer, node) {
2206
+ context.childBlockNodeIds.add(node.id);
2207
+ context.symbolTableExclusions.add(node.id);
2208
+ context.generatedBlockNodeIds.add(node.id);
2209
+ writer.writeLine(`// --- ${node.label} (${node.nodeType}) [${node.id}] ---`);
2210
+ generateNodeBody(writer, node, context);
2211
+ generateBlockContinuation(writer, node.id, context);
2212
+ },
2213
+ pushScope(nodeId, scopeVar) {
2214
+ context.scopeStack.push({ nodeId, scopeVar });
2215
+ },
2216
+ popScope() {
2217
+ context.scopeStack.pop();
2218
+ },
2219
+ __platformResponse: context.platform.generateResponse.bind(context.platform)
2220
+ };
2221
+ }
2222
+ function sanitizeId(id) {
2223
+ return id.replace(/[^a-zA-Z0-9_]/g, "_");
2224
+ }
2225
+ function resolveEnvVars(url, context) {
2226
+ const hasEnvVar = /\$\{(\w+)\}/.test(url);
2227
+ if (hasEnvVar) {
2228
+ return "`" + url.replace(/\$\{(\w+)\}/g, (_match, varName) => {
2229
+ context.envVars.add(varName);
2230
+ return "${process.env." + varName + "}";
2231
+ }) + "`";
2232
+ }
2233
+ if (url.includes("${")) {
2234
+ return "`" + url + "`";
2235
+ }
2236
+ return `"${url}"`;
2237
+ }
2238
+ function collectRequiredPackages(ir, context) {
2239
+ for (const node of ir.nodes) {
2240
+ const plugin = context.pluginRegistry.get(node.nodeType);
2241
+ if (plugin?.getRequiredPackages) {
2242
+ const packages = plugin.getRequiredPackages(node);
2243
+ packages.forEach((pkg) => context.requiredPackages.add(pkg));
2244
+ }
2245
+ }
2246
+ const platformDeps = context.platform.getImplicitDependencies();
2247
+ platformDeps.forEach((pkg) => context.requiredPackages.add(pkg));
2248
+ }
2249
+ function buildSourceMap(code, ir, filePath) {
2250
+ const lines = code.split("\n");
2251
+ const mappings = {};
2252
+ const nodeMarkerRegex = /^[\s]*\/\/ --- .+? \(.+?\) \[(.+?)\] ---$/;
2253
+ let currentNodeId = null;
2254
+ let currentStartLine = 0;
2255
+ for (let i = 0; i < lines.length; i++) {
2256
+ const lineNum = i + 1;
2257
+ const match = lines[i].match(nodeMarkerRegex);
2258
+ if (match) {
2259
+ if (currentNodeId) {
2260
+ mappings[currentNodeId] = {
2261
+ startLine: currentStartLine,
2262
+ endLine: lineNum - 1
2263
+ };
2264
+ }
2265
+ const [, nodeId] = match;
2266
+ if (ir.nodes.some((n) => n.id === nodeId)) {
2267
+ currentNodeId = nodeId;
2268
+ currentStartLine = lineNum;
2269
+ } else {
2270
+ currentNodeId = null;
2271
+ }
2272
+ }
2273
+ }
2274
+ if (currentNodeId) {
2275
+ mappings[currentNodeId] = {
2276
+ startLine: currentStartLine,
2277
+ endLine: lines.length
2278
+ };
2279
+ }
2280
+ const trigger = ir.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
2281
+ if (trigger && !mappings[trigger.id]) {
2282
+ for (let i = 0; i < lines.length; i++) {
2283
+ if (lines[i].includes("export async function") || lines[i].includes("@schedule")) {
2284
+ mappings[trigger.id] = { startLine: i + 1, endLine: lines.length };
2285
+ break;
2286
+ }
2287
+ }
2288
+ }
2289
+ return {
2290
+ version: 1,
2291
+ generatedFile: filePath,
2292
+ mappings
2293
+ };
2294
+ }
2295
+ function traceLineToNode(sourceMap, line) {
2296
+ for (const [nodeId, range] of Object.entries(sourceMap.mappings)) {
2297
+ if (line >= range.startLine && line <= range.endLine) {
2298
+ return { nodeId, ...range };
2299
+ }
2300
+ }
2301
+ return null;
2302
+ }
2303
+
2304
+ // src/lib/ir/security.ts
2305
+ var SECURITY_PATTERNS = [
2306
+ // ── Critical: Remote Code Execution / System Access ──
2307
+ { pattern: /\beval\s*\(/, desc: "eval() \u2014 dynamically executes arbitrary code", severity: "critical" },
2308
+ { pattern: /\bnew\s+Function\s*\(/, desc: "new Function() \u2014 dynamically constructs a function", severity: "critical" },
2309
+ { pattern: /\bchild_process\b/, desc: "child_process \u2014 can execute arbitrary system commands", severity: "critical" },
2310
+ { pattern: /\bexec\s*\(/, desc: "exec() \u2014 executes shell commands", severity: "critical" },
2311
+ { pattern: /\bexecSync\s*\(/, desc: "execSync() \u2014 synchronously executes shell commands", severity: "critical" },
2312
+ { pattern: /\bspawn\s*\(/, desc: "spawn() \u2014 spawns a child process", severity: "critical" },
2313
+ { pattern: /\bprocess\.exit\b/, desc: "process.exit() \u2014 terminates the Node.js process", severity: "critical" },
2314
+ { pattern: /\bprocess\.env\b/, desc: "process.env \u2014 accesses environment variables (may leak secrets)", severity: "critical" },
2315
+ { pattern: /\bprocess\.kill\b/, desc: "process.kill() \u2014 kills a process", severity: "critical" },
2316
+ { pattern: /\brequire\s*\(\s*['"`]child_process/, desc: "require('child_process')", severity: "critical" },
2317
+ { pattern: /\brequire\s*\(\s*['"`]vm['"`]/, desc: "require('vm') \u2014 V8 virtual machine", severity: "critical" },
2318
+ // ── Critical: File System Destructive ──
2319
+ { pattern: /\bfs\.\w*(unlink|rmdir|rm|rmSync|unlinkSync)\b/, desc: "fs delete operation", severity: "critical" },
2320
+ { pattern: /\bfs\.\w*(writeFile|writeFileSync|appendFile)\b/, desc: "fs write operation", severity: "critical" },
2321
+ { pattern: /\brequire\s*\(\s*['"`]fs['"`]\)/, desc: "require('fs') \u2014 file system access", severity: "critical" },
2322
+ // ── Warning: Network / Dynamic Import ──
2323
+ { pattern: /\bimport\s*\(/, desc: "dynamic import() \u2014 can load arbitrary modules", severity: "warning" },
2324
+ { pattern: /\brequire\s*\(\s*['"`]https?['"`]/, desc: "require('http/https') \u2014 network module", severity: "warning" },
2325
+ { pattern: /\brequire\s*\(\s*['"`]net['"`]/, desc: "require('net') \u2014 low-level network access", severity: "warning" },
2326
+ { pattern: /\bglobalThis\b/, desc: "globalThis \u2014 accesses the global scope", severity: "warning" },
2327
+ { pattern: /\b__proto__\b/, desc: "__proto__ \u2014 prototype pollution risk", severity: "warning" },
2328
+ { pattern: /\bconstructor\s*\[\s*['"`]/, desc: "constructor[] \u2014 prototype pollution risk", severity: "warning" },
2329
+ // ── Warning: FS Read (non-destructive but sensitive) ──
2330
+ { pattern: /\brequire\s*\(\s*['"`]fs['"`]\s*\)\.read/, desc: "fs read operation", severity: "warning" },
2331
+ { pattern: /\bfs\.\w*(readFile|readFileSync|readdir)\b/, desc: "fs read operation", severity: "warning" },
2332
+ // ── Info: Uncommon patterns ──
2333
+ { pattern: /\bsetTimeout\s*\(\s*[^,]+,\s*\d{5,}/, desc: "long setTimeout (>10s) \u2014 possibly a malicious delay", severity: "info" },
2334
+ { pattern: /\bwhile\s*\(\s*true\s*\)/, desc: "while(true) \u2014 possible infinite loop", severity: "info" },
2335
+ { pattern: /\bfor\s*\(\s*;\s*;\s*\)/, desc: "for(;;) \u2014 possible infinite loop", severity: "info" }
2336
+ ];
2337
+ function truncate(str, maxLen) {
2338
+ return str.length > maxLen ? str.slice(0, maxLen) + "\u2026" : str;
2339
+ }
2340
+ function scanCode(nodeId, nodeLabel, code) {
2341
+ const findings = [];
2342
+ for (const { pattern, desc, severity } of SECURITY_PATTERNS) {
2343
+ const globalPattern = new RegExp(pattern.source, "g");
2344
+ let match;
2345
+ while ((match = globalPattern.exec(code)) !== null) {
2346
+ findings.push({
2347
+ severity,
2348
+ nodeId,
2349
+ nodeLabel,
2350
+ pattern: desc,
2351
+ match: truncate(match[0], 80)
2352
+ });
2353
+ }
2354
+ }
2355
+ return findings;
2356
+ }
2357
+ function extractCodeFields(node) {
2358
+ const codes = [];
2359
+ const params = node.params;
2360
+ if (!params) return codes;
2361
+ if (node.nodeType === "custom_code" /* CUSTOM_CODE */ && typeof params.code === "string") {
2362
+ codes.push(params.code);
2363
+ }
2364
+ if (typeof params.expression === "string") {
2365
+ codes.push(params.expression);
2366
+ }
2367
+ if (typeof params.inputMapping === "string") {
2368
+ codes.push(params.inputMapping);
2369
+ } else if (typeof params.inputMapping === "object" && params.inputMapping !== null) {
2370
+ codes.push(JSON.stringify(params.inputMapping));
2371
+ }
2372
+ if (typeof params.body === "string") {
2373
+ codes.push(params.body);
2374
+ }
2375
+ if (typeof params.query === "string") {
2376
+ codes.push(params.query);
2377
+ }
2378
+ if (typeof params.bodyExpression === "string") {
2379
+ codes.push(params.bodyExpression);
2380
+ }
2381
+ if (typeof params.condition === "string") {
2382
+ codes.push(params.condition);
2383
+ }
2384
+ return codes;
2385
+ }
2386
+ function validateIRSecurity(ir) {
2387
+ const findings = [];
2388
+ let nodesScanned = 0;
2389
+ for (const node of ir.nodes) {
2390
+ const codeFields = extractCodeFields(node);
2391
+ if (codeFields.length === 0) continue;
2392
+ nodesScanned++;
2393
+ for (const code of codeFields) {
2394
+ const nodeFindings = scanCode(node.id, node.label ?? node.id, code);
2395
+ findings.push(...nodeFindings);
2396
+ }
2397
+ }
2398
+ const hasCritical = findings.some((f) => f.severity === "critical");
2399
+ return {
2400
+ safe: !hasCritical,
2401
+ findings,
2402
+ nodesScanned
2403
+ };
2404
+ }
2405
+ function formatSecurityReport(result) {
2406
+ if (result.findings.length === 0) {
2407
+ return `\u2705 Security check passed (scanned ${result.nodesScanned} nodes, no dangerous patterns detected)`;
2408
+ }
2409
+ const lines = [];
2410
+ const critical = result.findings.filter((f) => f.severity === "critical");
2411
+ const warnings = result.findings.filter((f) => f.severity === "warning");
2412
+ const infos = result.findings.filter((f) => f.severity === "info");
2413
+ lines.push(`\u26A0\uFE0F Security check results (scanned ${result.nodesScanned} nodes)`);
2414
+ lines.push("");
2415
+ if (critical.length > 0) {
2416
+ lines.push(`\u{1F534} Critical (${critical.length}):`);
2417
+ for (const f of critical) {
2418
+ lines.push(` [${f.nodeId}] ${f.pattern} \u2014 match: "${f.match}"`);
2419
+ }
2420
+ }
2421
+ if (warnings.length > 0) {
2422
+ lines.push(`\u{1F7E1} Warning (${warnings.length}):`);
2423
+ for (const f of warnings) {
2424
+ lines.push(` [${f.nodeId}] ${f.pattern} \u2014 match: "${f.match}"`);
2425
+ }
2426
+ }
2427
+ if (infos.length > 0) {
2428
+ lines.push(`\u{1F535} Info (${infos.length}):`);
2429
+ for (const f of infos) {
2430
+ lines.push(` [${f.nodeId}] ${f.pattern} \u2014 match: "${f.match}"`);
2431
+ }
2432
+ }
2433
+ return lines.join("\n");
2434
+ }
2435
+
2436
+ // src/lib/compiler/decompiler.ts
2437
+ var import_ts_morph2 = require("ts-morph");
2438
+ function decompile(code, options = {}) {
2439
+ const errors = [];
2440
+ const { fileName = "input.ts", audit: enableAudit = true } = options;
2441
+ try {
2442
+ const project = new import_ts_morph2.Project({ useInMemoryFileSystem: true });
2443
+ const sourceFile = project.createSourceFile(fileName, code);
2444
+ const targetFn = findTargetFunction(sourceFile, options.functionName);
2445
+ if (!targetFn) {
2446
+ errors.push("No exported function found to decompile");
2447
+ return { success: false, errors, confidence: 0 };
2448
+ }
2449
+ const ctx = createDecompileContext();
2450
+ const trigger = createTriggerFromFunction(targetFn, sourceFile, ctx);
2451
+ ctx.addNode(trigger);
2452
+ const body = targetFn.fn.getBody();
2453
+ if (body && body.getKind() === import_ts_morph2.SyntaxKind.Block) {
2454
+ walkBlock(body, trigger.id, ctx);
2455
+ }
2456
+ buildEdges(ctx);
2457
+ const auditHints = enableAudit ? computeAuditHints(ctx) : [];
2458
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2459
+ const ir = {
2460
+ version: CURRENT_IR_VERSION,
2461
+ meta: {
2462
+ name: targetFn.name ?? fileName.replace(/\.(ts|tsx|js|jsx)$/, ""),
2463
+ description: `Decompiled from ${fileName}`,
2464
+ createdAt: now,
2465
+ updatedAt: now
2466
+ },
2467
+ nodes: ctx.getNodes(),
2468
+ edges: ctx.getEdges()
2469
+ };
2470
+ const nodeCount = ir.nodes.length;
2471
+ const hasControlFlow = ir.nodes.some((n) => n.category === "logic" /* LOGIC */);
2472
+ const confidence = Math.min(
2473
+ 0.95,
2474
+ 0.3 + nodeCount * 0.08 + (hasControlFlow ? 0.15 : 0)
2475
+ );
2476
+ return {
2477
+ success: true,
2478
+ ir,
2479
+ errors: errors.length > 0 ? errors : void 0,
2480
+ confidence,
2481
+ audit: auditHints.length > 0 ? auditHints : void 0
2482
+ };
2483
+ } catch (err) {
2484
+ return {
2485
+ success: false,
2486
+ errors: [err instanceof Error ? err.message : String(err)],
2487
+ confidence: 0
2488
+ };
2489
+ }
2490
+ }
2491
+ function createDecompileContext() {
2492
+ const ctx = {
2493
+ nodes: /* @__PURE__ */ new Map(),
2494
+ edgeList: [],
2495
+ varDefs: /* @__PURE__ */ new Map(),
2496
+ varUses: /* @__PURE__ */ new Map(),
2497
+ seqPredecessors: /* @__PURE__ */ new Map(),
2498
+ controlFlowChildren: /* @__PURE__ */ new Map(),
2499
+ auditData: [],
2500
+ counters: {},
2501
+ nodeLines: /* @__PURE__ */ new Map(),
2502
+ addNode(node, line) {
2503
+ ctx.nodes.set(node.id, node);
2504
+ if (line !== void 0) ctx.nodeLines.set(node.id, line);
2505
+ },
2506
+ getNodes() {
2507
+ return Array.from(ctx.nodes.values());
2508
+ },
2509
+ getEdges() {
2510
+ return ctx.edgeList;
2511
+ },
2512
+ nextId(prefix) {
2513
+ ctx.counters[prefix] = (ctx.counters[prefix] ?? 0) + 1;
2514
+ return `${prefix}_${ctx.counters[prefix]}`;
2515
+ }
2516
+ };
2517
+ return ctx;
2518
+ }
2519
+ function findTargetFunction(sourceFile, targetName) {
2520
+ const httpMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
2521
+ if (targetName) {
2522
+ const fn = sourceFile.getFunction(targetName);
2523
+ if (fn) {
2524
+ const name = fn.getName();
2525
+ const httpMethod = name && httpMethods.includes(name.toUpperCase()) ? name.toUpperCase() : void 0;
2526
+ return { name: targetName, fn, httpMethod, isExported: fn.isExported() };
2527
+ }
2528
+ }
2529
+ for (const fn of sourceFile.getFunctions()) {
2530
+ if (!fn.isExported()) continue;
2531
+ const name = fn.getName();
2532
+ if (name && httpMethods.includes(name.toUpperCase())) {
2533
+ return { name, fn, httpMethod: name.toUpperCase(), isExported: true };
2534
+ }
2535
+ }
2536
+ for (const fn of sourceFile.getFunctions()) {
2537
+ if (fn.isExported()) {
2538
+ return { name: fn.getName(), fn, isExported: true };
2539
+ }
2540
+ }
2541
+ const allFns = sourceFile.getFunctions();
2542
+ if (allFns.length > 0) {
2543
+ const fn = allFns[0];
2544
+ return { name: fn.getName(), fn, isExported: fn.isExported() };
2545
+ }
2546
+ for (const stmt of sourceFile.getVariableStatements()) {
2547
+ if (!stmt.isExported()) continue;
2548
+ for (const decl of stmt.getDeclarations()) {
2549
+ const init = decl.getInitializer();
2550
+ if (init && init.getKind() === import_ts_morph2.SyntaxKind.ArrowFunction) {
2551
+ return {
2552
+ name: decl.getName(),
2553
+ fn: init,
2554
+ isExported: true
2555
+ };
2556
+ }
2557
+ }
2558
+ }
2559
+ return null;
2560
+ }
2561
+ function createTriggerFromFunction(target, sourceFile, ctx) {
2562
+ const id = ctx.nextId("trigger");
2563
+ if (target.httpMethod) {
2564
+ const routePath = inferRoutePath(sourceFile);
2565
+ const method = target.httpMethod;
2566
+ const parseBody = method !== "GET";
2567
+ return {
2568
+ id,
2569
+ nodeType: "http_webhook" /* HTTP_WEBHOOK */,
2570
+ category: "trigger" /* TRIGGER */,
2571
+ label: `${method} ${routePath}`,
2572
+ params: { method, routePath, parseBody },
2573
+ inputs: [],
2574
+ outputs: [
2575
+ { id: "request", label: "Request", dataType: "object" },
2576
+ { id: "body", label: "Body", dataType: "object" },
2577
+ { id: "query", label: "Query", dataType: "object" }
2578
+ ]
2579
+ };
2580
+ }
2581
+ return {
2582
+ id,
2583
+ nodeType: "manual" /* MANUAL */,
2584
+ category: "trigger" /* TRIGGER */,
2585
+ label: target.name ?? "Entry Point",
2586
+ params: {
2587
+ functionName: target.name ?? "handler",
2588
+ args: target.fn.getParameters?.() ? target.fn.getParameters().map((p) => ({
2589
+ name: p.getName(),
2590
+ type: inferDataType(p.getType().getText())
2591
+ })) : []
2592
+ },
2593
+ inputs: [],
2594
+ outputs: [{ id: "output", label: "Output", dataType: "any" }]
2595
+ };
2596
+ }
2597
+ function walkBlock(block, parentNodeId, ctx, controlFlowPort) {
2598
+ const statements = block.getStatements();
2599
+ let lastNodeId = parentNodeId;
2600
+ for (const stmt of statements) {
2601
+ const nodeId = processStatement(stmt, lastNodeId, ctx);
2602
+ if (nodeId) {
2603
+ if (lastNodeId !== parentNodeId || !controlFlowPort) {
2604
+ ctx.seqPredecessors.set(nodeId, lastNodeId);
2605
+ }
2606
+ if (lastNodeId === parentNodeId && controlFlowPort) {
2607
+ if (!ctx.controlFlowChildren.has(parentNodeId)) {
2608
+ ctx.controlFlowChildren.set(parentNodeId, []);
2609
+ }
2610
+ ctx.controlFlowChildren.get(parentNodeId).push({
2611
+ portId: controlFlowPort,
2612
+ nodeId
2613
+ });
2614
+ }
2615
+ lastNodeId = nodeId;
2616
+ }
2617
+ }
2618
+ }
2619
+ function processStatement(stmt, prevNodeId, ctx) {
2620
+ const kind = stmt.getKind();
2621
+ const line = stmt.getStartLineNumber();
2622
+ switch (kind) {
2623
+ case import_ts_morph2.SyntaxKind.VariableStatement:
2624
+ return processVariableStatement(stmt, prevNodeId, ctx, line);
2625
+ case import_ts_morph2.SyntaxKind.IfStatement:
2626
+ return processIfStatement(stmt, prevNodeId, ctx, line);
2627
+ case import_ts_morph2.SyntaxKind.ForOfStatement:
2628
+ return processForOfStatement(stmt, prevNodeId, ctx, line);
2629
+ case import_ts_morph2.SyntaxKind.ForInStatement:
2630
+ return processForInStatement(stmt, prevNodeId, ctx, line);
2631
+ case import_ts_morph2.SyntaxKind.ForStatement:
2632
+ return processForStatement(stmt, prevNodeId, ctx, line);
2633
+ case import_ts_morph2.SyntaxKind.TryStatement:
2634
+ return processTryStatement(stmt, prevNodeId, ctx, line);
2635
+ case import_ts_morph2.SyntaxKind.ReturnStatement:
2636
+ return processReturnStatement(stmt, prevNodeId, ctx, line);
2637
+ case import_ts_morph2.SyntaxKind.ExpressionStatement:
2638
+ return processExpressionStatement(stmt, prevNodeId, ctx, line);
2639
+ default:
2640
+ return null;
2641
+ }
2642
+ }
2643
+ function processVariableStatement(stmt, _prevNodeId, ctx, line) {
2644
+ const declarations = stmt.getDeclarations();
2645
+ if (declarations.length === 0) return null;
2646
+ const decl = declarations[0];
2647
+ return processVariableDeclaration(decl, ctx, line);
2648
+ }
2649
+ function processVariableDeclaration(decl, ctx, line) {
2650
+ const varName = decl.getName();
2651
+ const init = decl.getInitializer();
2652
+ if (!init) return null;
2653
+ const initText = init.getText();
2654
+ if (isFetchCall(init)) {
2655
+ const nodeId2 = ctx.nextId("fetch");
2656
+ const node = parseFetchNode(nodeId2, initText, varName);
2657
+ ctx.addNode(node, line);
2658
+ ctx.varDefs.set(varName, { nodeId: nodeId2, portId: "data", varName });
2659
+ return nodeId2;
2660
+ }
2661
+ if (hasAwaitExpression(init)) {
2662
+ const nodeId2 = ctx.nextId("async_op");
2663
+ const awaitedCode = extractAwaitedExpression(init);
2664
+ ctx.addNode({
2665
+ id: nodeId2,
2666
+ nodeType: "custom_code" /* CUSTOM_CODE */,
2667
+ category: "action" /* ACTION */,
2668
+ label: inferLabel(awaitedCode, varName),
2669
+ params: { code: awaitedCode, returnVariable: varName },
2670
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
2671
+ outputs: [{ id: "result", label: "Result", dataType: "any" }]
2672
+ }, line);
2673
+ ctx.varDefs.set(varName, { nodeId: nodeId2, portId: "result", varName });
2674
+ trackVariableUses(nodeId2, initText, ctx);
2675
+ return nodeId2;
2676
+ }
2677
+ if (isSimpleDeclaration(init)) {
2678
+ const nodeId2 = ctx.nextId("var");
2679
+ const dataType = inferDataType(decl.getType().getText());
2680
+ const isConst = decl.getVariableStatement()?.getDeclarationKind()?.toString() === "const";
2681
+ ctx.addNode({
2682
+ id: nodeId2,
2683
+ nodeType: "declare" /* DECLARE */,
2684
+ category: "variable" /* VARIABLE */,
2685
+ label: varName,
2686
+ params: {
2687
+ name: varName,
2688
+ dataType,
2689
+ initialValue: initText,
2690
+ isConst: isConst ?? true
2691
+ },
2692
+ inputs: [],
2693
+ outputs: [{ id: "value", label: "Value", dataType }]
2694
+ }, line);
2695
+ ctx.varDefs.set(varName, { nodeId: nodeId2, portId: "value", varName });
2696
+ trackVariableUses(nodeId2, initText, ctx);
2697
+ return nodeId2;
2698
+ }
2699
+ const nodeId = ctx.nextId("transform");
2700
+ ctx.addNode({
2701
+ id: nodeId,
2702
+ nodeType: "transform" /* TRANSFORM */,
2703
+ category: "variable" /* VARIABLE */,
2704
+ label: varName,
2705
+ params: { expression: initText },
2706
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: true }],
2707
+ outputs: [{ id: "output", label: "Output", dataType: "any" }]
2708
+ }, line);
2709
+ ctx.varDefs.set(varName, { nodeId, portId: "output", varName });
2710
+ trackVariableUses(nodeId, initText, ctx);
2711
+ return nodeId;
2712
+ }
2713
+ function processIfStatement(stmt, _prevNodeId, ctx, line) {
2714
+ const nodeId = ctx.nextId("if");
2715
+ const condition = stmt.getExpression().getText();
2716
+ ctx.addNode({
2717
+ id: nodeId,
2718
+ nodeType: "if_else" /* IF_ELSE */,
2719
+ category: "logic" /* LOGIC */,
2720
+ label: `if (${truncate2(condition, 40)})`,
2721
+ params: { condition },
2722
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: true }],
2723
+ outputs: [
2724
+ { id: "true", label: "True", dataType: "any" },
2725
+ { id: "false", label: "False", dataType: "any" }
2726
+ ]
2727
+ }, line);
2728
+ trackVariableUses(nodeId, condition, ctx);
2729
+ const thenBlock = stmt.getThenStatement();
2730
+ if (thenBlock.getKind() === import_ts_morph2.SyntaxKind.Block) {
2731
+ walkBlock(thenBlock, nodeId, ctx, "true");
2732
+ }
2733
+ const elseStmt = stmt.getElseStatement();
2734
+ if (elseStmt) {
2735
+ if (elseStmt.getKind() === import_ts_morph2.SyntaxKind.Block) {
2736
+ walkBlock(elseStmt, nodeId, ctx, "false");
2737
+ } else if (elseStmt.getKind() === import_ts_morph2.SyntaxKind.IfStatement) {
2738
+ const nestedId = processIfStatement(elseStmt, nodeId, ctx, elseStmt.getStartLineNumber());
2739
+ if (!ctx.controlFlowChildren.has(nodeId)) {
2740
+ ctx.controlFlowChildren.set(nodeId, []);
2741
+ }
2742
+ ctx.controlFlowChildren.get(nodeId).push({ portId: "false", nodeId: nestedId });
2743
+ }
2744
+ }
2745
+ if (!elseStmt) {
2746
+ ctx.auditData.push({
2747
+ nodeId,
2748
+ severity: "info",
2749
+ message: "If statement has no else branch \u2014 consider handling the negative case",
2750
+ line
2751
+ });
2752
+ }
2753
+ return nodeId;
2754
+ }
2755
+ function processForOfStatement(stmt, _prevNodeId, ctx, line) {
2756
+ const nodeId = ctx.nextId("loop");
2757
+ const initText = stmt.getInitializer().getText();
2758
+ const itemVar = initText.replace(/^(const|let|var)\s+/, "");
2759
+ const iterableExpr = stmt.getExpression().getText();
2760
+ ctx.addNode({
2761
+ id: nodeId,
2762
+ nodeType: "for_loop" /* FOR_LOOP */,
2763
+ category: "logic" /* LOGIC */,
2764
+ label: `for (${itemVar} of ${truncate2(iterableExpr, 30)})`,
2765
+ params: {
2766
+ iterableExpression: iterableExpr,
2767
+ itemVariable: itemVar
2768
+ },
2769
+ inputs: [{ id: "iterable", label: "Iterable", dataType: "array", required: true }],
2770
+ outputs: [
2771
+ { id: "item", label: "Item", dataType: "any" },
2772
+ { id: "result", label: "Result", dataType: "array" }
2773
+ ]
2774
+ }, line);
2775
+ trackVariableUses(nodeId, iterableExpr, ctx);
2776
+ const body = stmt.getStatement();
2777
+ if (body.getKind() === import_ts_morph2.SyntaxKind.Block) {
2778
+ walkBlock(body, nodeId, ctx, "item");
2779
+ }
2780
+ return nodeId;
2781
+ }
2782
+ function processForInStatement(stmt, _prevNodeId, ctx, line) {
2783
+ const nodeId = ctx.nextId("loop");
2784
+ const initText = stmt.getInitializer().getText();
2785
+ const itemVar = initText.replace(/^(const|let|var)\s+/, "");
2786
+ const iterableExpr = stmt.getExpression().getText();
2787
+ ctx.addNode({
2788
+ id: nodeId,
2789
+ nodeType: "for_loop" /* FOR_LOOP */,
2790
+ category: "logic" /* LOGIC */,
2791
+ label: `for (${itemVar} in ${truncate2(iterableExpr, 30)})`,
2792
+ params: {
2793
+ iterableExpression: `Object.keys(${iterableExpr})`,
2794
+ itemVariable: itemVar
2795
+ },
2796
+ inputs: [{ id: "iterable", label: "Iterable", dataType: "array", required: true }],
2797
+ outputs: [
2798
+ { id: "item", label: "Item", dataType: "any" },
2799
+ { id: "result", label: "Result", dataType: "array" }
2800
+ ]
2801
+ }, line);
2802
+ trackVariableUses(nodeId, iterableExpr, ctx);
2803
+ const body = stmt.getStatement();
2804
+ if (body.getKind() === import_ts_morph2.SyntaxKind.Block) {
2805
+ walkBlock(body, nodeId, ctx, "item");
2806
+ }
2807
+ return nodeId;
2808
+ }
2809
+ function processForStatement(stmt, _prevNodeId, ctx, line) {
2810
+ const nodeId = ctx.nextId("loop");
2811
+ const fullText = stmt.getText().split("{")[0].trim();
2812
+ ctx.addNode({
2813
+ id: nodeId,
2814
+ nodeType: "for_loop" /* FOR_LOOP */,
2815
+ category: "logic" /* LOGIC */,
2816
+ label: truncate2(fullText, 50),
2817
+ params: {
2818
+ iterableExpression: fullText,
2819
+ itemVariable: "i"
2820
+ },
2821
+ inputs: [{ id: "iterable", label: "Iterable", dataType: "array", required: true }],
2822
+ outputs: [
2823
+ { id: "item", label: "Item", dataType: "any" },
2824
+ { id: "result", label: "Result", dataType: "array" }
2825
+ ]
2826
+ }, line);
2827
+ const body = stmt.getStatement();
2828
+ if (body.getKind() === import_ts_morph2.SyntaxKind.Block) {
2829
+ walkBlock(body, nodeId, ctx, "item");
2830
+ }
2831
+ return nodeId;
2832
+ }
2833
+ function processTryStatement(stmt, _prevNodeId, ctx, line) {
2834
+ const nodeId = ctx.nextId("trycatch");
2835
+ const catchClause = stmt.getCatchClause();
2836
+ const errorVar = catchClause?.getVariableDeclaration()?.getName() ?? "error";
2837
+ ctx.addNode({
2838
+ id: nodeId,
2839
+ nodeType: "try_catch" /* TRY_CATCH */,
2840
+ category: "logic" /* LOGIC */,
2841
+ label: "Try / Catch",
2842
+ params: { errorVariable: errorVar },
2843
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: true }],
2844
+ outputs: [
2845
+ { id: "success", label: "Success", dataType: "any" },
2846
+ { id: "error", label: "Error", dataType: "object" }
2847
+ ]
2848
+ }, line);
2849
+ const tryBlock = stmt.getTryBlock();
2850
+ walkBlock(tryBlock, nodeId, ctx, "success");
2851
+ if (catchClause) {
2852
+ const catchBlock = catchClause.getBlock();
2853
+ walkBlock(catchBlock, nodeId, ctx, "error");
2854
+ }
2855
+ return nodeId;
2856
+ }
2857
+ function processReturnStatement(stmt, _prevNodeId, ctx, line) {
2858
+ const nodeId = ctx.nextId("response");
2859
+ const returnExpr = stmt.getExpression();
2860
+ const returnText = returnExpr?.getText() ?? "";
2861
+ const isHttpResponse = returnText.includes("NextResponse") || returnText.includes("Response(") || returnText.includes(".json(") || returnText.includes("res.status") || returnText.includes("res.json");
2862
+ if (isHttpResponse) {
2863
+ const statusMatch = returnText.match(/status[:\s(]+(\d{3})/);
2864
+ const bodyMatch = returnText.match(/\.json\((.+?)(?:,|\))/s);
2865
+ ctx.addNode({
2866
+ id: nodeId,
2867
+ nodeType: "return_response" /* RETURN_RESPONSE */,
2868
+ category: "output" /* OUTPUT */,
2869
+ label: `Response ${statusMatch?.[1] ?? "200"}`,
2870
+ params: {
2871
+ statusCode: statusMatch ? parseInt(statusMatch[1]) : 200,
2872
+ bodyExpression: bodyMatch?.[1]?.trim() ?? returnText
2873
+ },
2874
+ inputs: [{ id: "data", label: "Data", dataType: "any", required: true }],
2875
+ outputs: []
2876
+ }, line);
2877
+ } else {
2878
+ ctx.addNode({
2879
+ id: nodeId,
2880
+ nodeType: "return_response" /* RETURN_RESPONSE */,
2881
+ category: "output" /* OUTPUT */,
2882
+ label: "Return",
2883
+ params: {
2884
+ statusCode: 200,
2885
+ bodyExpression: returnText || "undefined"
2886
+ },
2887
+ inputs: [{ id: "data", label: "Data", dataType: "any", required: true }],
2888
+ outputs: []
2889
+ }, line);
2890
+ }
2891
+ trackVariableUses(nodeId, returnText, ctx);
2892
+ return nodeId;
2893
+ }
2894
+ function processExpressionStatement(stmt, _prevNodeId, ctx, line) {
2895
+ const expr = stmt.getExpression();
2896
+ const exprText = expr.getText();
2897
+ if (isFetchCall(expr)) {
2898
+ const nodeId = ctx.nextId("fetch");
2899
+ const node = parseFetchNode(nodeId, exprText, void 0);
2900
+ ctx.addNode(node, line);
2901
+ return nodeId;
2902
+ }
2903
+ if (hasAwaitExpression(expr)) {
2904
+ const nodeId = ctx.nextId("async_op");
2905
+ const awaitedCode = extractAwaitedExpression(expr);
2906
+ ctx.addNode({
2907
+ id: nodeId,
2908
+ nodeType: "custom_code" /* CUSTOM_CODE */,
2909
+ category: "action" /* ACTION */,
2910
+ label: inferLabel(awaitedCode, void 0),
2911
+ params: { code: awaitedCode },
2912
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
2913
+ outputs: [{ id: "result", label: "Result", dataType: "any" }]
2914
+ }, line);
2915
+ trackVariableUses(nodeId, exprText, ctx);
2916
+ return nodeId;
2917
+ }
2918
+ const flowStateMatch = exprText.match(/flowState\['([^']+)'\]\s*=\s*(.+)/s);
2919
+ if (flowStateMatch) {
2920
+ return null;
2921
+ }
2922
+ if (exprText.includes("res.status") || exprText.includes("res.json")) {
2923
+ const nodeId = ctx.nextId("response");
2924
+ const statusMatch = exprText.match(/status\((\d+)\)/);
2925
+ ctx.addNode({
2926
+ id: nodeId,
2927
+ nodeType: "return_response" /* RETURN_RESPONSE */,
2928
+ category: "output" /* OUTPUT */,
2929
+ label: `Response ${statusMatch?.[1] ?? "200"}`,
2930
+ params: {
2931
+ statusCode: statusMatch ? parseInt(statusMatch[1]) : 200,
2932
+ bodyExpression: exprText
2933
+ },
2934
+ inputs: [{ id: "data", label: "Data", dataType: "any", required: true }],
2935
+ outputs: []
2936
+ }, line);
2937
+ trackVariableUses(nodeId, exprText, ctx);
2938
+ return nodeId;
2939
+ }
2940
+ return null;
2941
+ }
2942
+ function buildEdges(ctx) {
2943
+ let edgeCounter = 0;
2944
+ const addEdge = (source, sourcePort, target, targetPort) => {
2945
+ const exists = ctx.edgeList.some(
2946
+ (e) => e.sourceNodeId === source && e.targetNodeId === target && e.sourcePortId === sourcePort
2947
+ );
2948
+ if (exists) return;
2949
+ ctx.edgeList.push({
2950
+ id: `e${++edgeCounter}`,
2951
+ sourceNodeId: source,
2952
+ sourcePortId: sourcePort,
2953
+ targetNodeId: target,
2954
+ targetPortId: targetPort
2955
+ });
2956
+ };
2957
+ const connectedTargets = /* @__PURE__ */ new Set();
2958
+ for (const [nodeId, uses] of ctx.varUses) {
2959
+ for (const use of uses) {
2960
+ const def = ctx.varDefs.get(use.varName);
2961
+ if (def && def.nodeId !== nodeId) {
2962
+ addEdge(def.nodeId, def.portId, nodeId, use.portId);
2963
+ connectedTargets.add(nodeId);
2964
+ }
2965
+ }
2966
+ }
2967
+ for (const [parentId, children] of ctx.controlFlowChildren) {
2968
+ for (const child of children) {
2969
+ addEdge(parentId, child.portId, child.nodeId, "input");
2970
+ connectedTargets.add(child.nodeId);
2971
+ }
2972
+ }
2973
+ for (const [nodeId, predId] of ctx.seqPredecessors) {
2974
+ if (connectedTargets.has(nodeId)) continue;
2975
+ const predNode = ctx.nodes.get(predId);
2976
+ if (!predNode) continue;
2977
+ const sourcePort = predNode.outputs[0]?.id ?? "output";
2978
+ addEdge(predId, sourcePort, nodeId, "input");
2979
+ }
2980
+ }
2981
+ function computeAuditHints(ctx) {
2982
+ const hints = [...ctx.auditData];
2983
+ for (const [nodeId, node] of ctx.nodes) {
2984
+ const line = ctx.nodeLines.get(nodeId);
2985
+ if (node.nodeType === "custom_code" /* CUSTOM_CODE */ || node.nodeType === "fetch_api" /* FETCH_API */) {
2986
+ const isInsideTryCatch = Array.from(ctx.controlFlowChildren.entries()).some(
2987
+ ([parentId, children]) => {
2988
+ const parent = ctx.nodes.get(parentId);
2989
+ return parent?.nodeType === "try_catch" /* TRY_CATCH */ && children.some((c) => c.nodeId === nodeId);
2990
+ }
2991
+ );
2992
+ let ancestorId = ctx.seqPredecessors.get(nodeId);
2993
+ let foundTryCatch = isInsideTryCatch;
2994
+ while (ancestorId && !foundTryCatch) {
2995
+ const ancestor = ctx.nodes.get(ancestorId);
2996
+ if (ancestor?.nodeType === "try_catch" /* TRY_CATCH */) foundTryCatch = true;
2997
+ for (const [parentId, children] of ctx.controlFlowChildren) {
2998
+ const parent = ctx.nodes.get(parentId);
2999
+ if (parent?.nodeType === "try_catch" /* TRY_CATCH */ && children.some((c) => c.nodeId === nodeId)) {
3000
+ foundTryCatch = true;
3001
+ }
3002
+ }
3003
+ ancestorId = ctx.seqPredecessors.get(ancestorId);
3004
+ }
3005
+ if (!foundTryCatch) {
3006
+ hints.push({
3007
+ nodeId,
3008
+ severity: "warning",
3009
+ message: `Async operation "${node.label}" has no error handling (missing try/catch)`,
3010
+ line
3011
+ });
3012
+ }
3013
+ }
3014
+ if (node.nodeType === "fetch_api" /* FETCH_API */) {
3015
+ hints.push({
3016
+ nodeId,
3017
+ severity: "info",
3018
+ message: "Consider checking response.ok or response.status after fetch",
3019
+ line
3020
+ });
3021
+ }
3022
+ }
3023
+ return hints;
3024
+ }
3025
+ function isFetchCall(node) {
3026
+ const text = node.getText();
3027
+ return text.includes("fetch(") && (text.includes("await") || node.getKind() === import_ts_morph2.SyntaxKind.AwaitExpression);
3028
+ }
3029
+ function hasAwaitExpression(node) {
3030
+ if (node.getKind() === import_ts_morph2.SyntaxKind.AwaitExpression) return true;
3031
+ return node.getText().startsWith("await ");
3032
+ }
3033
+ function extractAwaitedExpression(node) {
3034
+ const text = node.getText();
3035
+ if (text.startsWith("await ")) return text.slice(6);
3036
+ return text;
3037
+ }
3038
+ function isSimpleDeclaration(node) {
3039
+ const text = node.getText();
3040
+ return !text.includes("await") && !text.includes("fetch(") && (node.getKind() === import_ts_morph2.SyntaxKind.StringLiteral || node.getKind() === import_ts_morph2.SyntaxKind.NumericLiteral || node.getKind() === import_ts_morph2.SyntaxKind.TrueKeyword || node.getKind() === import_ts_morph2.SyntaxKind.FalseKeyword || node.getKind() === import_ts_morph2.SyntaxKind.NullKeyword || node.getKind() === import_ts_morph2.SyntaxKind.ArrayLiteralExpression || node.getKind() === import_ts_morph2.SyntaxKind.ObjectLiteralExpression || node.getKind() === import_ts_morph2.SyntaxKind.NewExpression);
3041
+ }
3042
+ function parseFetchNode(nodeId, text, varName) {
3043
+ const urlMatch = text.match(/fetch\(([^,)]+)/);
3044
+ let url = urlMatch?.[1]?.trim() ?? '""';
3045
+ url = url.replace(/^[`"']|[`"']$/g, "");
3046
+ const methodMatch = text.match(/method:\s*["'](\w+)["']/);
3047
+ const method = methodMatch?.[1]?.toUpperCase() ?? "GET";
3048
+ const headerMatch = text.match(/headers:\s*(\{[^}]+\})/);
3049
+ let headers;
3050
+ if (headerMatch) {
3051
+ headers = {};
3052
+ const headerPairs = headerMatch[1].matchAll(/"([^"]+)":\s*"([^"]+)"/g);
3053
+ for (const pair of headerPairs) {
3054
+ headers[pair[1]] = pair[2];
3055
+ }
3056
+ }
3057
+ return {
3058
+ id: nodeId,
3059
+ nodeType: "fetch_api" /* FETCH_API */,
3060
+ category: "action" /* ACTION */,
3061
+ label: `Fetch ${truncate2(url, 30) || varName || "API"}`,
3062
+ params: {
3063
+ url,
3064
+ method,
3065
+ headers,
3066
+ parseJson: text.includes(".json()")
3067
+ },
3068
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
3069
+ outputs: [
3070
+ { id: "response", label: "Response", dataType: "object" },
3071
+ { id: "data", label: "Data", dataType: "any" }
3072
+ ]
3073
+ };
3074
+ }
3075
+ function inferRoutePath(sourceFile) {
3076
+ const filePath = sourceFile.getFilePath();
3077
+ const appRouterMatch = filePath.match(/\/app\/api\/(.+?)\/route\.(ts|js)/);
3078
+ if (appRouterMatch) return `/api/${appRouterMatch[1]}`;
3079
+ const pagesMatch = filePath.match(/\/pages\/api\/(.+?)\.(ts|js)/);
3080
+ if (pagesMatch) return `/api/${pagesMatch[1]}`;
3081
+ const fullText = sourceFile.getFullText();
3082
+ const routeMatch = fullText.match(/\/api\/\S+/);
3083
+ if (routeMatch) return routeMatch[0];
3084
+ return "/api/handler";
3085
+ }
3086
+ function inferDataType(tsType) {
3087
+ if (tsType.includes("string")) return "string";
3088
+ if (tsType.includes("number")) return "number";
3089
+ if (tsType.includes("boolean")) return "boolean";
3090
+ if (tsType.includes("[]") || tsType.includes("Array")) return "array";
3091
+ if (tsType.includes("{") || tsType.includes("Record") || tsType.includes("object")) return "object";
3092
+ return "any";
3093
+ }
3094
+ function inferLabel(code, varName) {
3095
+ const callMatch = code.match(/^(\w+(?:\.\w+)*)\(/);
3096
+ if (callMatch) return callMatch[1];
3097
+ if (varName) return varName;
3098
+ return truncate2(code, 30);
3099
+ }
3100
+ function trackVariableUses(nodeId, expression, ctx) {
3101
+ const identifiers = expression.match(/\b([a-zA-Z_]\w*)\b/g);
3102
+ if (!identifiers) return;
3103
+ const uses = [];
3104
+ const seen = /* @__PURE__ */ new Set();
3105
+ for (const ident of identifiers) {
3106
+ if (SKIP_IDENTIFIERS.has(ident)) continue;
3107
+ if (seen.has(ident)) continue;
3108
+ seen.add(ident);
3109
+ if (ctx.varDefs.has(ident)) {
3110
+ uses.push({ nodeId, portId: "input", varName: ident });
3111
+ }
3112
+ }
3113
+ if (uses.length > 0) {
3114
+ const existing = ctx.varUses.get(nodeId) ?? [];
3115
+ ctx.varUses.set(nodeId, [...existing, ...uses]);
3116
+ }
3117
+ }
3118
+ var SKIP_IDENTIFIERS = /* @__PURE__ */ new Set([
3119
+ // JS keywords
3120
+ "const",
3121
+ "let",
3122
+ "var",
3123
+ "function",
3124
+ "return",
3125
+ "if",
3126
+ "else",
3127
+ "for",
3128
+ "while",
3129
+ "do",
3130
+ "switch",
3131
+ "case",
3132
+ "break",
3133
+ "continue",
3134
+ "throw",
3135
+ "try",
3136
+ "catch",
3137
+ "finally",
3138
+ "new",
3139
+ "delete",
3140
+ "typeof",
3141
+ "void",
3142
+ "in",
3143
+ "of",
3144
+ "instanceof",
3145
+ "this",
3146
+ "super",
3147
+ "class",
3148
+ "extends",
3149
+ "import",
3150
+ "export",
3151
+ "default",
3152
+ "from",
3153
+ "as",
3154
+ "async",
3155
+ "await",
3156
+ "yield",
3157
+ "true",
3158
+ "false",
3159
+ "null",
3160
+ "undefined",
3161
+ // Common globals
3162
+ "console",
3163
+ "JSON",
3164
+ "Math",
3165
+ "Date",
3166
+ "Error",
3167
+ "Promise",
3168
+ "Array",
3169
+ "Object",
3170
+ "String",
3171
+ "Number",
3172
+ "Boolean",
3173
+ "Map",
3174
+ "Set",
3175
+ "RegExp",
3176
+ "Symbol",
3177
+ "Buffer",
3178
+ "process",
3179
+ "setTimeout",
3180
+ "setInterval",
3181
+ "clearTimeout",
3182
+ "clearInterval",
3183
+ "fetch",
3184
+ "Response",
3185
+ "Request",
3186
+ "Headers",
3187
+ "URL",
3188
+ "URLSearchParams",
3189
+ "NextResponse",
3190
+ "NextRequest",
3191
+ // Common methods
3192
+ "log",
3193
+ "error",
3194
+ "warn",
3195
+ "stringify",
3196
+ "parse",
3197
+ "json",
3198
+ "text",
3199
+ "toString",
3200
+ "map",
3201
+ "filter",
3202
+ "reduce",
3203
+ "forEach",
3204
+ "find",
3205
+ "some",
3206
+ "every",
3207
+ "includes",
3208
+ "push",
3209
+ "pop",
3210
+ "shift",
3211
+ "unshift",
3212
+ "slice",
3213
+ "splice",
3214
+ "concat",
3215
+ "join",
3216
+ "keys",
3217
+ "values",
3218
+ "entries",
3219
+ "assign",
3220
+ "freeze",
3221
+ "status",
3222
+ "ok",
3223
+ "headers",
3224
+ "body",
3225
+ "method",
3226
+ "url",
3227
+ "length",
3228
+ "trim",
3229
+ "split",
3230
+ "replace",
3231
+ "match",
3232
+ "test",
3233
+ "exec",
3234
+ "parseInt",
3235
+ "parseFloat",
3236
+ "isNaN",
3237
+ "isFinite",
3238
+ "then",
3239
+ "catch",
3240
+ "finally"
3241
+ ]);
3242
+ function truncate2(str, maxLen) {
3243
+ if (str.length <= maxLen) return str;
3244
+ return str.slice(0, maxLen - 3) + "...";
3245
+ }
3246
+
3247
+ // src/lib/compiler/runtime-tracer.ts
3248
+ function traceError(error, sourceMap, ir, editorUrl = "http://localhost:3001") {
3249
+ const results = [];
3250
+ if (!error.stack) return results;
3251
+ const generatedFile = sourceMap.generatedFile;
3252
+ const lineRegex = new RegExp(
3253
+ `(?:${escapeRegex2(generatedFile)}|generated\\.ts):(\\d+)`,
3254
+ "g"
3255
+ );
3256
+ let match;
3257
+ const seenNodes = /* @__PURE__ */ new Set();
3258
+ while ((match = lineRegex.exec(error.stack)) !== null) {
3259
+ const lineNum = parseInt(match[1], 10);
3260
+ const trace = traceLineToNode(sourceMap, lineNum);
3261
+ if (trace && !seenNodes.has(trace.nodeId)) {
3262
+ seenNodes.add(trace.nodeId);
3263
+ const node = ir?.nodes.find((n) => n.id === trace.nodeId);
3264
+ results.push({
3265
+ nodeId: trace.nodeId,
3266
+ nodeLabel: node?.label ?? trace.nodeId,
3267
+ nodeType: node?.nodeType ?? "unknown",
3268
+ startLine: trace.startLine,
3269
+ endLine: trace.endLine,
3270
+ deepLink: `${editorUrl}?highlight=${encodeURIComponent(trace.nodeId)}`
3271
+ });
3272
+ }
3273
+ }
3274
+ return results;
3275
+ }
3276
+ function formatTraceResults(error, traces, generatedFile) {
3277
+ if (traces.length === 0) {
3278
+ return `\u274C ${error.message}
3279
+ (no source map match)`;
3280
+ }
3281
+ const lines = [`\u274C Runtime Error: ${error.message}`];
3282
+ for (const t of traces) {
3283
+ lines.push(
3284
+ ` \u2192 Flow Node: [${t.nodeId}] "${t.nodeLabel}" (${t.nodeType})`,
3285
+ ` \u2192 Lines: ${t.startLine}-${t.endLine} in ${generatedFile}`,
3286
+ ` \u2192 \u{1F517} ${t.deepLink}`
3287
+ );
3288
+ }
3289
+ return lines.join("\n");
3290
+ }
3291
+ function withFlowTrace(handler, options) {
3292
+ const {
3293
+ editorUrl = "http://localhost:3001",
3294
+ sourceMap,
3295
+ ir,
3296
+ log = true
3297
+ } = options;
3298
+ if (!sourceMap) return handler;
3299
+ const wrapped = async (...args) => {
3300
+ try {
3301
+ return await handler(...args);
3302
+ } catch (err) {
3303
+ if (err instanceof Error) {
3304
+ const traces = traceError(err, sourceMap, ir, editorUrl);
3305
+ if (log && traces.length > 0) {
3306
+ console.error(
3307
+ formatTraceResults(err, traces, sourceMap.generatedFile)
3308
+ );
3309
+ }
3310
+ }
3311
+ throw err;
3312
+ }
3313
+ };
3314
+ return wrapped;
3315
+ }
3316
+ function installFlowTracer(options) {
3317
+ const { sourceMap, ir, editorUrl = "http://localhost:3001", log = true } = options;
3318
+ const handleError = (err) => {
3319
+ if (!(err instanceof Error)) return;
3320
+ const traces = traceError(err, sourceMap, ir, editorUrl);
3321
+ if (log && traces.length > 0) {
3322
+ console.error(
3323
+ formatTraceResults(err, traces, sourceMap.generatedFile)
3324
+ );
3325
+ }
3326
+ };
3327
+ process.on("uncaughtException", handleError);
3328
+ process.on("unhandledRejection", handleError);
3329
+ return () => {
3330
+ process.removeListener("uncaughtException", handleError);
3331
+ process.removeListener("unhandledRejection", handleError);
3332
+ };
3333
+ }
3334
+ function escapeRegex2(str) {
3335
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3336
+ }
3337
+
3338
+ // src/lib/node-registry.ts
3339
+ var NodeRegistry = class {
3340
+ definitions = /* @__PURE__ */ new Map();
3341
+ /** Register a single node definition */
3342
+ register(def) {
3343
+ this.definitions.set(def.nodeType, def);
3344
+ }
3345
+ /** Batch register */
3346
+ registerAll(defs) {
3347
+ for (const def of defs) {
3348
+ this.register(def);
3349
+ }
3350
+ }
3351
+ /** Get a single node definition */
3352
+ get(nodeType) {
3353
+ return this.definitions.get(nodeType);
3354
+ }
3355
+ /** Get all node definitions */
3356
+ getAll() {
3357
+ return [...this.definitions.values()];
3358
+ }
3359
+ /** Get node definitions by category */
3360
+ getByCategory(category) {
3361
+ return this.getAll().filter((d) => d.category === category);
3362
+ }
3363
+ /** Get all registered nodeType strings */
3364
+ getRegisteredTypes() {
3365
+ return [...this.definitions.keys()];
3366
+ }
3367
+ /** Check if registered */
3368
+ has(nodeType) {
3369
+ return this.definitions.has(nodeType);
3370
+ }
3371
+ /** Remove (for testing or hot reload) */
3372
+ unregister(nodeType) {
3373
+ return this.definitions.delete(nodeType);
3374
+ }
3375
+ /** Clear all (for testing) */
3376
+ clear() {
3377
+ this.definitions.clear();
3378
+ }
3379
+ // ── Convenience methods: backward-compatible with old API ──
3380
+ /** Get node default ports (backward-compatible with getDefaultPorts) */
3381
+ getDefaultPorts(nodeType) {
3382
+ const def = this.definitions.get(nodeType);
3383
+ return def?.defaultPorts ?? { inputs: [], outputs: [] };
3384
+ }
3385
+ /** Get node default parameters (backward-compatible with getDefaultParams) */
3386
+ getDefaultParams(nodeType) {
3387
+ const def = this.definitions.get(nodeType);
3388
+ return def?.defaultParams ?? {};
3389
+ }
3390
+ /** Get node default label (backward-compatible with getDefaultLabel) */
3391
+ getDefaultLabel(nodeType) {
3392
+ const def = this.definitions.get(nodeType);
3393
+ return def?.label ?? "Unknown";
3394
+ }
3395
+ /** Infer category from node type (backward-compatible with getCategoryForType) */
3396
+ getCategoryForType(nodeType) {
3397
+ const def = this.definitions.get(nodeType);
3398
+ return def?.category ?? "action" /* ACTION */;
3399
+ }
3400
+ /**
3401
+ * Group nodes by group (for NodeLibrary UI)
3402
+ * Return format is compatible with the original NodeLibrary.tsx nodeTemplates
3403
+ */
3404
+ getGroupedDefinitions() {
3405
+ const defaultGroups = {
3406
+ ["trigger" /* TRIGGER */]: { icon: "\u26A1", color: "text-emerald-400", name: "Triggers" },
3407
+ ["action" /* ACTION */]: { icon: "\u{1F527}", color: "text-blue-400", name: "Actions" },
3408
+ ["logic" /* LOGIC */]: { icon: "\u{1F500}", color: "text-amber-400", name: "Logic Control" },
3409
+ ["variable" /* VARIABLE */]: { icon: "\u{1F4E6}", color: "text-purple-400", name: "Variables" },
3410
+ ["output" /* OUTPUT */]: { icon: "\u{1F4E4}", color: "text-rose-400", name: "Output" }
3411
+ };
3412
+ const groups = {};
3413
+ const sorted = this.getAll().sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
3414
+ for (const def of sorted) {
3415
+ const groupInfo = defaultGroups[def.category];
3416
+ const groupName = def.group ?? groupInfo?.name ?? def.category;
3417
+ if (!groups[groupName]) {
3418
+ groups[groupName] = {
3419
+ icon: groupInfo?.icon ?? "\u{1F4E6}",
3420
+ color: groupInfo?.color ?? "text-gray-400",
3421
+ templates: []
3422
+ };
3423
+ }
3424
+ groups[groupName].templates.push({
3425
+ nodeType: def.nodeType,
3426
+ label: def.label,
3427
+ icon: def.icon,
3428
+ category: def.category
3429
+ });
3430
+ }
3431
+ return groups;
3432
+ }
3433
+ };
3434
+ var nodeRegistry = new NodeRegistry();
3435
+ var builtinNodeDefinitions = [
3436
+ // ── Triggers ──
3437
+ {
3438
+ nodeType: "http_webhook" /* HTTP_WEBHOOK */,
3439
+ category: "trigger" /* TRIGGER */,
3440
+ label: "HTTP Webhook",
3441
+ icon: "\u{1F310}",
3442
+ description: "Listen for HTTP requests",
3443
+ order: 1,
3444
+ defaultPorts: {
3445
+ inputs: [],
3446
+ outputs: [
3447
+ { id: "request", label: "Request", dataType: "object" },
3448
+ { id: "body", label: "Body", dataType: "object" },
3449
+ { id: "query", label: "Query", dataType: "object" }
3450
+ ]
3451
+ },
3452
+ defaultParams: { method: "POST", routePath: "/api/endpoint", parseBody: true }
3453
+ },
3454
+ {
3455
+ nodeType: "cron_job" /* CRON_JOB */,
3456
+ category: "trigger" /* TRIGGER */,
3457
+ label: "Cron Job",
3458
+ icon: "\u23F0",
3459
+ description: "Scheduled cron trigger",
3460
+ order: 2,
3461
+ defaultPorts: {
3462
+ inputs: [],
3463
+ outputs: [{ id: "output", label: "Output", dataType: "any" }]
3464
+ },
3465
+ defaultParams: { schedule: "0 * * * *", functionName: "cronHandler" }
3466
+ },
3467
+ {
3468
+ nodeType: "manual" /* MANUAL */,
3469
+ category: "trigger" /* TRIGGER */,
3470
+ label: "Manual Trigger",
3471
+ icon: "\u{1F464}",
3472
+ description: "Manual trigger",
3473
+ order: 3,
3474
+ defaultPorts: {
3475
+ inputs: [],
3476
+ outputs: [{ id: "output", label: "Output", dataType: "any" }]
3477
+ },
3478
+ defaultParams: { functionName: "handler", args: [] }
3479
+ },
3480
+ // ── Actions ──
3481
+ {
3482
+ nodeType: "fetch_api" /* FETCH_API */,
3483
+ category: "action" /* ACTION */,
3484
+ label: "Fetch API",
3485
+ icon: "\u{1F4E1}",
3486
+ description: "Call external HTTP API",
3487
+ order: 10,
3488
+ defaultPorts: {
3489
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
3490
+ outputs: [
3491
+ { id: "response", label: "Response", dataType: "object" },
3492
+ { id: "data", label: "Data", dataType: "any" }
3493
+ ]
3494
+ },
3495
+ defaultParams: { url: "https://api.example.com", method: "GET", parseJson: true }
3496
+ },
3497
+ {
3498
+ nodeType: "sql_query" /* SQL_QUERY */,
3499
+ category: "action" /* ACTION */,
3500
+ label: "SQL Query",
3501
+ icon: "\u{1F5C4}\uFE0F",
3502
+ description: "Execute SQL query",
3503
+ order: 11,
3504
+ defaultPorts: {
3505
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
3506
+ outputs: [{ id: "result", label: "Result", dataType: "array" }]
3507
+ },
3508
+ defaultParams: { orm: "drizzle", query: "SELECT * FROM users", params: [] }
3509
+ },
3510
+ {
3511
+ nodeType: "redis_cache" /* REDIS_CACHE */,
3512
+ category: "action" /* ACTION */,
3513
+ label: "Redis Cache",
3514
+ icon: "\u{1F4BE}",
3515
+ description: "Access Redis cache",
3516
+ order: 12,
3517
+ defaultPorts: {
3518
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
3519
+ outputs: [{ id: "value", label: "Value", dataType: "any" }]
3520
+ },
3521
+ defaultParams: { operation: "get", key: "cache_key" }
3522
+ },
3523
+ {
3524
+ nodeType: "custom_code" /* CUSTOM_CODE */,
3525
+ category: "action" /* ACTION */,
3526
+ label: "Custom Code",
3527
+ icon: "\u{1F4BB}",
3528
+ description: "Custom TypeScript code",
3529
+ order: 13,
3530
+ defaultPorts: {
3531
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
3532
+ outputs: [{ id: "result", label: "Result", dataType: "any" }]
3533
+ },
3534
+ defaultParams: { code: "// your code here", returnVariable: "result" }
3535
+ },
3536
+ {
3537
+ nodeType: "call_subflow" /* CALL_SUBFLOW */,
3538
+ category: "action" /* ACTION */,
3539
+ label: "Call Subflow",
3540
+ icon: "\u{1F517}",
3541
+ description: "Call subflow",
3542
+ order: 14,
3543
+ defaultPorts: {
3544
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
3545
+ outputs: [{ id: "result", label: "Result", dataType: "any" }]
3546
+ },
3547
+ defaultParams: { flowPath: "./sub-flow", functionName: "subHandler", inputMapping: {} }
3548
+ },
3549
+ // ── Logic ──
3550
+ {
3551
+ nodeType: "if_else" /* IF_ELSE */,
3552
+ category: "logic" /* LOGIC */,
3553
+ label: "If / Else",
3554
+ icon: "\u{1F500}",
3555
+ description: "Conditional branch",
3556
+ order: 20,
3557
+ defaultPorts: {
3558
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: true }],
3559
+ outputs: [
3560
+ { id: "true", label: "True", dataType: "any" },
3561
+ { id: "false", label: "False", dataType: "any" }
3562
+ ]
3563
+ },
3564
+ defaultParams: { condition: "data !== null" }
3565
+ },
3566
+ {
3567
+ nodeType: "for_loop" /* FOR_LOOP */,
3568
+ category: "logic" /* LOGIC */,
3569
+ label: "For Loop",
3570
+ icon: "\u{1F501}",
3571
+ description: "Iterate array",
3572
+ order: 21,
3573
+ defaultPorts: {
3574
+ inputs: [{ id: "iterable", label: "Iterable", dataType: "array", required: true }],
3575
+ outputs: [
3576
+ { id: "item", label: "Item", dataType: "any" },
3577
+ { id: "result", label: "Result", dataType: "array" }
3578
+ ]
3579
+ },
3580
+ defaultParams: { iterableExpression: "items", itemVariable: "item" }
3581
+ },
3582
+ {
3583
+ nodeType: "try_catch" /* TRY_CATCH */,
3584
+ category: "logic" /* LOGIC */,
3585
+ label: "Try / Catch",
3586
+ icon: "\u{1F6E1}\uFE0F",
3587
+ description: "Error handling wrapper",
3588
+ order: 22,
3589
+ defaultPorts: {
3590
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: true }],
3591
+ outputs: [
3592
+ { id: "success", label: "Success", dataType: "any" },
3593
+ { id: "error", label: "Error", dataType: "object" }
3594
+ ]
3595
+ },
3596
+ defaultParams: { errorVariable: "error" }
3597
+ },
3598
+ {
3599
+ nodeType: "promise_all" /* PROMISE_ALL */,
3600
+ category: "logic" /* LOGIC */,
3601
+ label: "Promise.all",
3602
+ icon: "\u26A1",
3603
+ description: "Execute multiple async tasks in parallel",
3604
+ order: 23,
3605
+ defaultPorts: {
3606
+ inputs: [
3607
+ { id: "task1", label: "Task 1", dataType: "any", required: true },
3608
+ { id: "task2", label: "Task 2", dataType: "any", required: true }
3609
+ ],
3610
+ outputs: [{ id: "results", label: "Results", dataType: "array" }]
3611
+ },
3612
+ defaultParams: {}
3613
+ },
3614
+ // ── Variables ──
3615
+ {
3616
+ nodeType: "declare" /* DECLARE */,
3617
+ category: "variable" /* VARIABLE */,
3618
+ label: "Declare Variable",
3619
+ icon: "\u{1F4E6}",
3620
+ description: "Declare variable",
3621
+ order: 30,
3622
+ defaultPorts: {
3623
+ inputs: [],
3624
+ outputs: [{ id: "value", label: "Value", dataType: "any" }]
3625
+ },
3626
+ defaultParams: { name: "myVar", dataType: "string", isConst: true, initialValue: "''" }
3627
+ },
3628
+ {
3629
+ nodeType: "transform" /* TRANSFORM */,
3630
+ category: "variable" /* VARIABLE */,
3631
+ label: "Transform",
3632
+ icon: "\u{1F504}",
3633
+ description: "Transform data",
3634
+ order: 31,
3635
+ defaultPorts: {
3636
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: true }],
3637
+ outputs: [{ id: "output", label: "Output", dataType: "any" }]
3638
+ },
3639
+ defaultParams: { expression: "input.map(x => x)" }
3640
+ },
3641
+ // ── Output ──
3642
+ {
3643
+ nodeType: "return_response" /* RETURN_RESPONSE */,
3644
+ category: "output" /* OUTPUT */,
3645
+ label: "Return Response",
3646
+ icon: "\u{1F4E4}",
3647
+ description: "Return HTTP response",
3648
+ order: 40,
3649
+ defaultPorts: {
3650
+ inputs: [{ id: "data", label: "Data", dataType: "any", required: true }],
3651
+ outputs: []
3652
+ },
3653
+ defaultParams: { statusCode: 200, bodyExpression: "{{$input}}" }
3654
+ }
3655
+ ];
3656
+ nodeRegistry.registerAll(builtinNodeDefinitions);
3657
+
3658
+ // src/lib/storage/split-storage.ts
3659
+ var import_yaml = require("yaml");
3660
+ function splitIR(ir) {
3661
+ const meta = {
3662
+ version: ir.version,
3663
+ name: ir.meta.name,
3664
+ description: ir.meta.description,
3665
+ createdAt: ir.meta.createdAt,
3666
+ updatedAt: ir.meta.updatedAt,
3667
+ nodeOrder: ir.nodes.map((n) => n.id)
3668
+ };
3669
+ const metaYaml = addHeader("Flow2Code Meta", (0, import_yaml.stringify)(meta, { lineWidth: 120 }));
3670
+ const edgesData = ir.edges.map((e) => ({
3671
+ id: e.id,
3672
+ source: `${e.sourceNodeId}:${e.sourcePortId}`,
3673
+ target: `${e.targetNodeId}:${e.targetPortId}`
3674
+ }));
3675
+ const edgesYaml = addHeader(
3676
+ "Flow2Code Edges",
3677
+ (0, import_yaml.stringify)(edgesData, { lineWidth: 120 })
3678
+ );
3679
+ const nodes = /* @__PURE__ */ new Map();
3680
+ for (const node of ir.nodes) {
3681
+ const filename = `${sanitizeFilename(node.id)}.yaml`;
3682
+ const nodeData = {
3683
+ id: node.id,
3684
+ nodeType: node.nodeType,
3685
+ category: node.category,
3686
+ label: node.label,
3687
+ params: node.params,
3688
+ inputs: node.inputs,
3689
+ outputs: node.outputs
3690
+ };
3691
+ nodes.set(filename, addHeader(`Node: ${node.label}`, (0, import_yaml.stringify)(nodeData, { lineWidth: 120 })));
3692
+ }
3693
+ return { meta: metaYaml, edges: edgesYaml, nodes };
3694
+ }
3695
+ function mergeIR(files) {
3696
+ const meta = (0, import_yaml.parse)(files.meta);
3697
+ const nodeMap = /* @__PURE__ */ new Map();
3698
+ for (const [_filename, yaml] of files.nodes) {
3699
+ const node = (0, import_yaml.parse)(yaml);
3700
+ nodeMap.set(node.id, node);
3701
+ }
3702
+ const orderedNodes = [];
3703
+ const seen = /* @__PURE__ */ new Set();
3704
+ if (meta.nodeOrder) {
3705
+ for (const id of meta.nodeOrder) {
3706
+ const node = nodeMap.get(id);
3707
+ if (node) {
3708
+ orderedNodes.push(node);
3709
+ seen.add(id);
3710
+ }
3711
+ }
3712
+ }
3713
+ for (const [id, node] of nodeMap) {
3714
+ if (!seen.has(id)) {
3715
+ orderedNodes.push(node);
3716
+ }
3717
+ }
3718
+ const edgesRaw = (0, import_yaml.parse)(files.edges);
3719
+ const edges = (edgesRaw ?? []).map((e) => {
3720
+ const [sourceNodeId, sourcePortId] = e.source.split(":");
3721
+ const [targetNodeId, targetPortId] = e.target.split(":");
3722
+ return {
3723
+ id: e.id,
3724
+ sourceNodeId,
3725
+ sourcePortId,
3726
+ targetNodeId,
3727
+ targetPortId
3728
+ };
3729
+ });
3730
+ return {
3731
+ version: meta.version,
3732
+ meta: {
3733
+ name: meta.name,
3734
+ description: meta.description,
3735
+ createdAt: meta.createdAt,
3736
+ updatedAt: meta.updatedAt
3737
+ },
3738
+ nodes: orderedNodes,
3739
+ edges
3740
+ };
3741
+ }
3742
+ function addHeader(title, content) {
3743
+ return `# ${title}
3744
+ # Generated by Flow2Code
3745
+
3746
+ ${content}`;
3747
+ }
3748
+ function sanitizeFilename(id) {
3749
+ return id.replace(/[^a-zA-Z0-9_-]/g, "_");
3750
+ }
3751
+
3752
+ // src/lib/storage/flow-project.ts
3753
+ var import_node_fs = require("fs");
3754
+ var import_node_path = require("path");
3755
+ function detectFormat(inputPath) {
3756
+ if (inputPath.endsWith(".flow.json") && (0, import_node_fs.existsSync)(inputPath)) {
3757
+ return { resolvedPath: inputPath, format: "json" };
3758
+ }
3759
+ if ((0, import_node_fs.existsSync)(inputPath) && (0, import_node_fs.statSync)(inputPath).isDirectory()) {
3760
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(inputPath, "meta.yaml"))) {
3761
+ return { resolvedPath: inputPath, format: "split" };
3762
+ }
3763
+ }
3764
+ const jsonPath = inputPath.endsWith(".json") ? inputPath : `${inputPath}.flow.json`;
3765
+ if ((0, import_node_fs.existsSync)(jsonPath)) {
3766
+ return { resolvedPath: jsonPath, format: "json" };
3767
+ }
3768
+ if ((0, import_node_fs.existsSync)(inputPath) && (0, import_node_fs.statSync)(inputPath).isDirectory()) {
3769
+ return { resolvedPath: inputPath, format: "split" };
3770
+ }
3771
+ return { resolvedPath: inputPath, format: "split" };
3772
+ }
3773
+ function loadFlowProject(inputPath) {
3774
+ const { resolvedPath, format } = detectFormat(inputPath);
3775
+ if (format === "json") {
3776
+ if (!(0, import_node_fs.existsSync)(resolvedPath)) {
3777
+ throw new Error(`Flow file not found: ${resolvedPath}`);
3778
+ }
3779
+ const raw = (0, import_node_fs.readFileSync)(resolvedPath, "utf-8");
3780
+ const ir2 = JSON.parse(raw);
3781
+ return { path: resolvedPath, format, ir: ir2 };
3782
+ }
3783
+ if (!(0, import_node_fs.existsSync)(resolvedPath)) {
3784
+ throw new Error(`Flow directory not found: ${resolvedPath}`);
3785
+ }
3786
+ const metaPath = (0, import_node_path.join)(resolvedPath, "meta.yaml");
3787
+ if (!(0, import_node_fs.existsSync)(metaPath)) {
3788
+ throw new Error(`meta.yaml not found in ${resolvedPath} \u2014 not a valid Flow directory`);
3789
+ }
3790
+ const meta = (0, import_node_fs.readFileSync)(metaPath, "utf-8");
3791
+ const edgesPath = (0, import_node_path.join)(resolvedPath, "edges.yaml");
3792
+ const edges = (0, import_node_fs.existsSync)(edgesPath) ? (0, import_node_fs.readFileSync)(edgesPath, "utf-8") : "";
3793
+ const nodesDir = (0, import_node_path.join)(resolvedPath, "nodes");
3794
+ const nodes = /* @__PURE__ */ new Map();
3795
+ if ((0, import_node_fs.existsSync)(nodesDir)) {
3796
+ const nodeFiles = (0, import_node_fs.readdirSync)(nodesDir).filter((f) => f.endsWith(".yaml"));
3797
+ for (const file of nodeFiles) {
3798
+ nodes.set(file, (0, import_node_fs.readFileSync)((0, import_node_path.join)(nodesDir, file), "utf-8"));
3799
+ }
3800
+ }
3801
+ const ir = mergeIR({ meta, edges, nodes });
3802
+ return { path: resolvedPath, format, ir };
3803
+ }
3804
+ function saveFlowProject(ir, outputPath, options = {}) {
3805
+ const { format = "split", cleanOrphanNodes = true } = options;
3806
+ if (format === "json") {
3807
+ const jsonPath = outputPath.endsWith(".flow.json") ? outputPath : `${outputPath}.flow.json`;
3808
+ const dir = (0, import_node_path.dirname)(jsonPath);
3809
+ if (!(0, import_node_fs.existsSync)(dir)) {
3810
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
3811
+ }
3812
+ (0, import_node_fs.writeFileSync)(jsonPath, JSON.stringify(ir, null, 2), "utf-8");
3813
+ return [jsonPath];
3814
+ }
3815
+ const dirPath = outputPath.endsWith(".flow.json") ? outputPath.replace(/\.flow\.json$/, "") : outputPath;
3816
+ (0, import_node_fs.mkdirSync)(dirPath, { recursive: true });
3817
+ const nodesDir = (0, import_node_path.join)(dirPath, "nodes");
3818
+ (0, import_node_fs.mkdirSync)(nodesDir, { recursive: true });
3819
+ const files = splitIR(ir);
3820
+ const written = [];
3821
+ const metaPath = (0, import_node_path.join)(dirPath, "meta.yaml");
3822
+ (0, import_node_fs.writeFileSync)(metaPath, files.meta, "utf-8");
3823
+ written.push(metaPath);
3824
+ const edgesPath = (0, import_node_path.join)(dirPath, "edges.yaml");
3825
+ (0, import_node_fs.writeFileSync)(edgesPath, files.edges, "utf-8");
3826
+ written.push(edgesPath);
3827
+ const newNodeFiles = /* @__PURE__ */ new Set();
3828
+ for (const [filename, content] of files.nodes) {
3829
+ const nodePath = (0, import_node_path.join)(nodesDir, filename);
3830
+ (0, import_node_fs.writeFileSync)(nodePath, content, "utf-8");
3831
+ written.push(nodePath);
3832
+ newNodeFiles.add(filename);
3833
+ }
3834
+ if (cleanOrphanNodes && (0, import_node_fs.existsSync)(nodesDir)) {
3835
+ const existing = (0, import_node_fs.readdirSync)(nodesDir).filter((f) => f.endsWith(".yaml"));
3836
+ for (const file of existing) {
3837
+ if (!newNodeFiles.has(file)) {
3838
+ (0, import_node_fs.rmSync)((0, import_node_path.join)(nodesDir, file));
3839
+ }
3840
+ }
3841
+ }
3842
+ return written;
3843
+ }
3844
+ function migrateToSplit(jsonPath) {
3845
+ if (!(0, import_node_fs.existsSync)(jsonPath)) {
3846
+ throw new Error(`File not found: ${jsonPath}`);
3847
+ }
3848
+ const raw = (0, import_node_fs.readFileSync)(jsonPath, "utf-8");
3849
+ const ir = JSON.parse(raw);
3850
+ const dirPath = jsonPath.replace(/\.flow\.json$/, "");
3851
+ return saveFlowProject(ir, dirPath, { format: "split" });
3852
+ }
3853
+
3854
+ // src/lib/diff/semantic-diff.ts
3855
+ function semanticDiff(before, after) {
3856
+ const changes = [];
3857
+ diffMeta(before, after, changes);
3858
+ diffNodes(before.nodes, after.nodes, changes);
3859
+ diffEdges(before.edges, after.edges, changes);
3860
+ const stats = {
3861
+ added: changes.filter((c) => c.type === "added").length,
3862
+ removed: changes.filter((c) => c.type === "removed").length,
3863
+ modified: changes.filter((c) => c.type === "modified").length,
3864
+ total: changes.length
3865
+ };
3866
+ return { changes, stats };
3867
+ }
3868
+ function diffMeta(before, after, changes) {
3869
+ const details = [];
3870
+ if (before.meta.name !== after.meta.name) {
3871
+ details.push({
3872
+ field: "meta.name",
3873
+ before: before.meta.name,
3874
+ after: after.meta.name
3875
+ });
3876
+ }
3877
+ if (before.meta.description !== after.meta.description) {
3878
+ details.push({
3879
+ field: "meta.description",
3880
+ before: before.meta.description,
3881
+ after: after.meta.description
3882
+ });
3883
+ }
3884
+ if (before.version !== after.version) {
3885
+ details.push({
3886
+ field: "version",
3887
+ before: before.version,
3888
+ after: after.version
3889
+ });
3890
+ }
3891
+ if (details.length > 0) {
3892
+ changes.push({
3893
+ type: "modified",
3894
+ category: "meta",
3895
+ id: "meta",
3896
+ description: `Workflow meta changed: ${details.map((d) => d.field).join(", ")}`,
3897
+ details
3898
+ });
3899
+ }
3900
+ }
3901
+ function diffNodes(beforeNodes, afterNodes, changes) {
3902
+ const beforeMap = new Map(
3903
+ beforeNodes.map((n) => [n.id, n])
3904
+ );
3905
+ const afterMap = new Map(
3906
+ afterNodes.map((n) => [n.id, n])
3907
+ );
3908
+ for (const [id, node] of afterMap) {
3909
+ if (!beforeMap.has(id)) {
3910
+ changes.push({
3911
+ type: "added",
3912
+ category: "node",
3913
+ id,
3914
+ description: `Added node: "${node.label}" (${node.nodeType}, ${node.category})`
3915
+ });
3916
+ }
3917
+ }
3918
+ for (const [id, node] of beforeMap) {
3919
+ if (!afterMap.has(id)) {
3920
+ changes.push({
3921
+ type: "removed",
3922
+ category: "node",
3923
+ id,
3924
+ description: `Removed node: "${node.label}" (${node.nodeType}, ${node.category})`
3925
+ });
3926
+ }
3927
+ }
3928
+ for (const [id, beforeNode] of beforeMap) {
3929
+ const afterNode = afterMap.get(id);
3930
+ if (!afterNode) continue;
3931
+ const details = diffNodeFields(beforeNode, afterNode);
3932
+ if (details.length > 0) {
3933
+ changes.push({
3934
+ type: "modified",
3935
+ category: "node",
3936
+ id,
3937
+ description: `Modified node: "${beforeNode.label}" \u2014 ${summarizeFieldChanges(details)}`,
3938
+ details
3939
+ });
3940
+ }
3941
+ }
3942
+ }
3943
+ function diffNodeFields(before, after) {
3944
+ const diffs = [];
3945
+ if (before.label !== after.label) {
3946
+ diffs.push({ field: "label", before: before.label, after: after.label });
3947
+ }
3948
+ if (before.nodeType !== after.nodeType) {
3949
+ diffs.push({
3950
+ field: "nodeType",
3951
+ before: before.nodeType,
3952
+ after: after.nodeType
3953
+ });
3954
+ }
3955
+ if (before.category !== after.category) {
3956
+ diffs.push({
3957
+ field: "category",
3958
+ before: before.category,
3959
+ after: after.category
3960
+ });
3961
+ }
3962
+ const paramDiffs = deepDiff(
3963
+ before.params,
3964
+ after.params,
3965
+ "params"
3966
+ );
3967
+ diffs.push(...paramDiffs);
3968
+ return diffs;
3969
+ }
3970
+ function diffEdges(beforeEdges, afterEdges, changes) {
3971
+ const beforeMap = new Map(beforeEdges.map((e) => [e.id, e]));
3972
+ const afterMap = new Map(afterEdges.map((e) => [e.id, e]));
3973
+ for (const [id, edge] of afterMap) {
3974
+ if (!beforeMap.has(id)) {
3975
+ changes.push({
3976
+ type: "added",
3977
+ category: "edge",
3978
+ id,
3979
+ description: `Added edge: ${edge.sourceNodeId}:${edge.sourcePortId} \u2192 ${edge.targetNodeId}:${edge.targetPortId}`
3980
+ });
3981
+ }
3982
+ }
3983
+ for (const [id, edge] of beforeMap) {
3984
+ if (!afterMap.has(id)) {
3985
+ changes.push({
3986
+ type: "removed",
3987
+ category: "edge",
3988
+ id,
3989
+ description: `Removed edge: ${edge.sourceNodeId}:${edge.sourcePortId} \u2192 ${edge.targetNodeId}:${edge.targetPortId}`
3990
+ });
3991
+ }
3992
+ }
3993
+ for (const [id, beforeEdge] of beforeMap) {
3994
+ const afterEdge = afterMap.get(id);
3995
+ if (!afterEdge) continue;
3996
+ const details = [];
3997
+ if (beforeEdge.sourceNodeId !== afterEdge.sourceNodeId) {
3998
+ details.push({
3999
+ field: "sourceNodeId",
4000
+ before: beforeEdge.sourceNodeId,
4001
+ after: afterEdge.sourceNodeId
4002
+ });
4003
+ }
4004
+ if (beforeEdge.sourcePortId !== afterEdge.sourcePortId) {
4005
+ details.push({
4006
+ field: "sourcePortId",
4007
+ before: beforeEdge.sourcePortId,
4008
+ after: afterEdge.sourcePortId
4009
+ });
4010
+ }
4011
+ if (beforeEdge.targetNodeId !== afterEdge.targetNodeId) {
4012
+ details.push({
4013
+ field: "targetNodeId",
4014
+ before: beforeEdge.targetNodeId,
4015
+ after: afterEdge.targetNodeId
4016
+ });
4017
+ }
4018
+ if (beforeEdge.targetPortId !== afterEdge.targetPortId) {
4019
+ details.push({
4020
+ field: "targetPortId",
4021
+ before: beforeEdge.targetPortId,
4022
+ after: afterEdge.targetPortId
4023
+ });
4024
+ }
4025
+ if (details.length > 0) {
4026
+ changes.push({
4027
+ type: "modified",
4028
+ category: "edge",
4029
+ id,
4030
+ description: `Modified edge: ${beforeEdge.sourceNodeId} \u2192 ${beforeEdge.targetNodeId} \u2014 ${summarizeFieldChanges(details)}`,
4031
+ details
4032
+ });
4033
+ }
4034
+ }
4035
+ }
4036
+ function deepDiff(before, after, prefix) {
4037
+ const diffs = [];
4038
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
4039
+ for (const key of allKeys) {
4040
+ const path = `${prefix}.${key}`;
4041
+ const bVal = before[key];
4042
+ const aVal = after[key];
4043
+ if (bVal === void 0 && aVal !== void 0) {
4044
+ diffs.push({ field: path, before: void 0, after: aVal });
4045
+ } else if (bVal !== void 0 && aVal === void 0) {
4046
+ diffs.push({ field: path, before: bVal, after: void 0 });
4047
+ } else if (typeof bVal === "object" && typeof aVal === "object" && bVal !== null && aVal !== null && !Array.isArray(bVal) && !Array.isArray(aVal)) {
4048
+ diffs.push(
4049
+ ...deepDiff(
4050
+ bVal,
4051
+ aVal,
4052
+ path
4053
+ )
4054
+ );
4055
+ } else if (JSON.stringify(bVal) !== JSON.stringify(aVal)) {
4056
+ diffs.push({ field: path, before: bVal, after: aVal });
4057
+ }
4058
+ }
4059
+ return diffs;
4060
+ }
4061
+ function summarizeFieldChanges(details) {
4062
+ if (details.length === 1) {
4063
+ const d = details[0];
4064
+ return `${d.field} changed from "${String(d.before)}" to "${String(d.after)}"`;
4065
+ }
4066
+ return `${details.length} field(s) changed (${details.map((d) => d.field).join(", ")})`;
4067
+ }
4068
+ function formatDiff(summary) {
4069
+ const lines = [];
4070
+ const { changes, stats } = summary;
4071
+ if (stats.total === 0) {
4072
+ return "\u2705 No differences";
4073
+ }
4074
+ lines.push(
4075
+ `\u{1F4CA} Diff summary: +${stats.added} added, -${stats.removed} removed, \u270F\uFE0F ${stats.modified} modified`
4076
+ );
4077
+ lines.push("");
4078
+ const metaChanges = changes.filter((c) => c.category === "meta");
4079
+ const nodeChanges = changes.filter((c) => c.category === "node");
4080
+ const edgeChanges = changes.filter((c) => c.category === "edge");
4081
+ if (metaChanges.length > 0) {
4082
+ lines.push("\u2500\u2500 Meta \u2500\u2500");
4083
+ for (const change of metaChanges) {
4084
+ lines.push(` ${getChangeIcon(change.type)} ${change.description}`);
4085
+ formatDetails(change.details, lines);
4086
+ }
4087
+ lines.push("");
4088
+ }
4089
+ if (nodeChanges.length > 0) {
4090
+ lines.push("\u2500\u2500 Nodes \u2500\u2500");
4091
+ for (const change of nodeChanges) {
4092
+ lines.push(` ${getChangeIcon(change.type)} ${change.description}`);
4093
+ formatDetails(change.details, lines);
4094
+ }
4095
+ lines.push("");
4096
+ }
4097
+ if (edgeChanges.length > 0) {
4098
+ lines.push("\u2500\u2500 Edges \u2500\u2500");
4099
+ for (const change of edgeChanges) {
4100
+ lines.push(` ${getChangeIcon(change.type)} ${change.description}`);
4101
+ formatDetails(change.details, lines);
4102
+ }
4103
+ lines.push("");
4104
+ }
4105
+ return lines.join("\n").trimEnd();
4106
+ }
4107
+ function formatDetails(details, lines) {
4108
+ if (!details || details.length === 0) return;
4109
+ for (const d of details) {
4110
+ const before = formatValue(d.before);
4111
+ const after = formatValue(d.after);
4112
+ lines.push(` ${d.field}: ${before} \u2192 ${after}`);
4113
+ }
4114
+ }
4115
+ function formatValue(value) {
4116
+ if (value === void 0) return "(unset)";
4117
+ if (value === null) return "null";
4118
+ if (typeof value === "string") return `"${value}"`;
4119
+ if (typeof value === "object") return JSON.stringify(value);
4120
+ return String(value);
4121
+ }
4122
+ function getChangeIcon(type) {
4123
+ switch (type) {
4124
+ case "added":
4125
+ return "\u{1F7E2}";
4126
+ case "removed":
4127
+ return "\u{1F534}";
4128
+ case "modified":
4129
+ return "\u{1F7E1}";
4130
+ }
4131
+ }
4132
+ // Annotate the CommonJS export names for ESM import in node:
4133
+ 0 && (module.exports = {
4134
+ ActionType,
4135
+ CURRENT_IR_VERSION,
4136
+ LogicType,
4137
+ NodeCategory,
4138
+ NodeRegistry,
4139
+ OutputType,
4140
+ TriggerType,
4141
+ VariableType,
4142
+ builtinPlugins,
4143
+ clearPlugins,
4144
+ compile,
4145
+ createPluginRegistry,
4146
+ decompile,
4147
+ detectFormat,
4148
+ formatDiff,
4149
+ formatSecurityReport,
4150
+ formatTraceResults,
4151
+ getAllPlugins,
4152
+ getAvailablePlatforms,
4153
+ getPlatform,
4154
+ getPlugin,
4155
+ hasPlugin,
4156
+ inferFlowStateTypes,
4157
+ installFlowTracer,
4158
+ loadFlowProject,
4159
+ mergeIR,
4160
+ migrateToSplit,
4161
+ nodeRegistry,
4162
+ parseExpression,
4163
+ registerPlatform,
4164
+ registerPlugin,
4165
+ registerPlugins,
4166
+ saveFlowProject,
4167
+ semanticDiff,
4168
+ splitIR,
4169
+ topologicalSort,
4170
+ traceError,
4171
+ traceLineToNode,
4172
+ validate,
4173
+ validateFlowIR,
4174
+ validateIRSecurity,
4175
+ withFlowTrace
4176
+ });
4177
+ //# sourceMappingURL=compiler.cjs.map