@timo9378/flow2code 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +15 -2
  3. package/dist/cli.js +74 -7
  4. package/dist/compiler.cjs +32 -2
  5. package/dist/compiler.d.cts +6 -0
  6. package/dist/compiler.d.ts +6 -0
  7. package/dist/compiler.js +32 -2
  8. package/dist/server.d.ts +1 -1
  9. package/dist/server.js +1227 -1010
  10. package/out/404.html +1 -1
  11. package/out/__next.__PAGE__.txt +4 -4
  12. package/out/__next._full.txt +13 -13
  13. package/out/__next._head.txt +4 -4
  14. package/out/__next._index.txt +6 -6
  15. package/out/__next._tree.txt +2 -2
  16. package/out/_next/static/chunks/05328cd26bdc795c.js +176 -0
  17. package/out/_next/static/chunks/06e01c846ae01892.js +1 -0
  18. package/out/_next/static/chunks/1011f174944c0ca2.js +70 -0
  19. package/out/_next/static/chunks/1570e9ba5f1b44ed.js +5 -0
  20. package/out/_next/static/chunks/6167fccccde2e675.css +1 -0
  21. package/out/_next/static/chunks/{b112c2f519e4b429.js → 7cd04052abfadac1.js} +1 -1
  22. package/out/_next/static/chunks/8091c1216a95d294.js +1 -0
  23. package/out/_next/static/chunks/98d53aae29c36c6b.js +1 -0
  24. package/out/_next/static/chunks/a6dad97d9634a72d.js.map +1 -1
  25. package/out/_next/static/chunks/{b163b5d7cccbcf42.js → b05daf00cdc6058f.js} +1 -1
  26. package/out/_next/static/chunks/b3419ee3e3a616d9.js +1 -0
  27. package/out/_next/static/chunks/{acf223168ac429f7.js → be40d79540010a0d.js} +1 -1
  28. package/out/_next/static/chunks/{turbopack-576234c945ffdc44.js → turbopack-9da9810f42c97265.js} +1 -1
  29. package/out/_not-found/__next._full.txt +11 -11
  30. package/out/_not-found/__next._head.txt +4 -4
  31. package/out/_not-found/__next._index.txt +6 -6
  32. package/out/_not-found/__next._not-found/__PAGE__.txt +2 -2
  33. package/out/_not-found/__next._not-found.txt +3 -3
  34. package/out/_not-found/__next._tree.txt +2 -2
  35. package/out/_not-found.html +1 -1
  36. package/out/_not-found.txt +11 -11
  37. package/out/index.html +2 -2
  38. package/out/index.txt +13 -13
  39. package/package.json +130 -124
  40. package/out/_next/static/chunks/0bc0a50347ee5f3c.js +0 -51
  41. package/out/_next/static/chunks/58bf94a9d7047ec0.js +0 -125
  42. package/out/_next/static/chunks/6b84376656bd9887.js +0 -1
  43. package/out/_next/static/chunks/83ab8820627f8bfe.css +0 -1
  44. package/out/_next/static/chunks/ab8888d4b78b94be.js +0 -5
  45. package/out/_next/static/chunks/b6e8711267bccbbd.js +0 -1
  46. package/out/_next/static/chunks/fbca595129527827.js +0 -1
  47. package/scripts/publish-all.sh +0 -56
  48. /package/out/_next/static/{Ma0MmC8j1mxpQbtLwNajF → dSp6-CaehEDKVU9OSJABu}/_buildManifest.js +0 -0
  49. /package/out/_next/static/{Ma0MmC8j1mxpQbtLwNajF → dSp6-CaehEDKVU9OSJABu}/_clientMiddlewareManifest.json +0 -0
  50. /package/out/_next/static/{Ma0MmC8j1mxpQbtLwNajF → dSp6-CaehEDKVU9OSJABu}/_ssgManifest.js +0 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [0.2.1] — 2026-03-05
9
+
10
+ ### Fixed
11
+ - **Unified API handlers** — All 4 Next.js API routes (`/api/compile`, `/api/decompile`, `/api/generate`, `/api/import-openapi`) are now thin wrappers delegating to `src/server/handlers.ts`, eliminating duplicated logic and drift risk.
12
+ - **Bundle size 97% reduction** — `server.js` 5.5MB → 145KB, `cli.js` 5.5MB → 191KB. Root cause was Prettier (not ts-morph) being inlined; externalized in `tsup.config.ts`.
13
+ - **OpenAPI tag-based filtering** — `/api/import-openapi` tag filter now works correctly (previously was a `return true` TODO stub). Fixed by delegating to `handlers.ts` which has a proper implementation checking `flow.meta.tags`.
14
+
15
+ ## [0.2.0] — 2026-03-05
16
+
17
+ ### Added — VSCode Extension (`vscode-extension/`)
18
+ - **Right-click Decompile** — Right-click any `.ts`/`.js` file (or selection) → "Flow2Code: Decompile to Flow IR" generates `.flow.json` side-by-side with confidence score
19
+ - **Right-click Compile** — Right-click any `.flow.json` → "Flow2Code: Compile to TypeScript" with configurable platform (Next.js / Express / Cloudflare)
20
+ - **Flow Preview** — SVG-based DAG visualization with pan, zoom, fit-to-view, category-colored nodes, and hover tooltips
21
+ - **Auto-Validation Diagnostics** — Inline errors/warnings on open and save for `.flow.json` files, positioned at the offending node/edge in JSON
22
+ - **Custom Editor** — "Open With… > Flow2Code Visual Editor" provides read-only graphical view of `.flow.json` files
23
+ - **Status Bar** — Shows flow name and node/edge count (`$(graph) MyFlow (5N·4E)`) when a `.flow.json` is active; click to preview
24
+ - **Configurable Settings** — `flow2code.platform`, `flow2code.autoValidate`, `flow2code.compileOnSave`
25
+ - **esbuild Bundling** — Self-contained 6.3MB bundle with `@/` alias plugin resolving to main project source
26
+
27
+ ### Added — Playwright E2E Testing
28
+ - **20 Playwright E2E tests** — Smoke tests, Node Operations, Toolbar Actions, API Compile Endpoint
29
+ - **Playwright config** — Chromium browser with auto-start dev server on port 3000
30
+
31
+ ### Fixed
32
+ - **Infinite re-render loop** — `FlowNode` badge selector `?? []` created a new array on every store update, causing infinite React setState loop with React Flow dimension measurements. Fixed by using a stable empty array constant.
33
+ - **`useFlowLint` unconditional updates** — Lint hook always called `setNodeBadges()` even when results hadn't changed, feeding the re-render loop. Added shallow comparison before updating.
34
+ - **Platform registration tree-shaking** — Turbopack tree-shook barrel file side effects, causing "Unknown platform" errors in API routes. Fixed by registering platforms directly in `compiler.ts`.
35
+
8
36
  ## [0.1.9] — 2026-03-05
9
37
 
10
38
  ### Fixed
package/README.md CHANGED
@@ -53,7 +53,7 @@
53
53
  | Platform Adapters | NextjsPlatform / ExpressPlatform / CloudflarePlatform |
54
54
  | Plugin System | `createPluginRegistry()` factory (per-instance) |
55
55
  | CLI | Commander.js + Chokidar |
56
- | Testing | Vitest 4 — 354 tests across 31 test files |
56
+ | Testing | Vitest 4 — 413 tests across 33 test files + 20 Playwright E2E tests |
57
57
  | CI | GitHub Actions (Node 20/22 matrix) |
58
58
 
59
59
  ## Quick Start
@@ -185,7 +185,7 @@ flow2code/
185
185
  │ │ └── storage/ # .flow.json split/merge
186
186
  │ ├── cli/ # CLI (compile/watch/init)
187
187
  │ └── server/ # Standalone HTTP Server
188
- ├── tests/ # 354 tests (Vitest)
188
+ ├── tests/ # 413 unit tests + 20 E2E tests
189
189
  ├── .github/workflows/ci.yml # GitHub Actions CI
190
190
  ├── CONTRIBUTING.md
191
191
  └── vitest.config.ts
@@ -225,6 +225,19 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
225
225
 
226
226
  For detailed usage examples and workflows, see [USAGE.md](USAGE.md).
227
227
 
228
+ ## VS Code Extension
229
+
230
+ Flow2Code ships a companion VS Code extension under `vscode-extension/`:
231
+
232
+ - **Right-click Decompile** — Decompile any `.ts`/`.js` to a `.flow.json` visual IR
233
+ - **Right-click Compile** — Compile `.flow.json` to TypeScript with platform selection
234
+ - **Flow Preview** — SVG-based DAG visualization with pan, zoom, category coloring
235
+ - **Auto-Validation** — Inline diagnostics for `.flow.json` on open/save
236
+ - **Custom Editor** — "Open With… > Flow2Code Visual Editor" for graphical view
237
+ - **Status Bar** — Shows node/edge count; click to preview
238
+
239
+ See [vscode-extension/README.md](vscode-extension/README.md) for details.
240
+
228
241
  ## License
229
242
 
230
243
  MIT
package/dist/cli.js CHANGED
@@ -205,14 +205,38 @@ function validateFlowIR(ir) {
205
205
  });
206
206
  }
207
207
  }
208
+ for (const edge of workingIR.edges) {
209
+ const sourceNode = workingNodeMap.get(edge.sourceNodeId);
210
+ const targetNode = workingNodeMap.get(edge.targetNodeId);
211
+ if (!sourceNode || !targetNode) continue;
212
+ const sourcePort = sourceNode.outputs?.find((p) => p.id === edge.sourcePortId);
213
+ const targetPort = targetNode.inputs?.find((p) => p.id === edge.targetPortId);
214
+ if (!sourcePort || !targetPort) continue;
215
+ if (!isTypeCompatible(sourcePort.dataType, targetPort.dataType)) {
216
+ errors.push({
217
+ code: "TYPE_MISMATCH",
218
+ message: `Type mismatch on edge "${edge.id}": output "${sourcePort.label}" (${sourcePort.dataType}) \u2192 input "${targetPort.label}" (${targetPort.dataType})`,
219
+ edgeId: edge.id,
220
+ severity: "warning"
221
+ });
222
+ }
223
+ }
208
224
  return {
209
- valid: errors.length === 0,
225
+ valid: errors.filter((e) => e.severity !== "warning" && e.severity !== "info").length === 0,
210
226
  errors,
211
227
  migrated,
212
228
  migratedIR: migrated ? workingIR : void 0,
213
229
  migrationLog
214
230
  };
215
231
  }
232
+ function isTypeCompatible(source, target) {
233
+ if (source === target) return true;
234
+ if (source === "any" || target === "any") return true;
235
+ if (target === "object" && source === "array") return true;
236
+ if (target === "string" && source === "number") return true;
237
+ if (target === "number" && source === "string") return true;
238
+ return false;
239
+ }
216
240
  function detectCycles(nodes, edges) {
217
241
  const errors = [];
218
242
  const adjacency = /* @__PURE__ */ new Map();
@@ -1077,6 +1101,9 @@ var init_platforms = __esm({
1077
1101
  "src/lib/compiler/platforms/index.ts"() {
1078
1102
  "use strict";
1079
1103
  init_types2();
1104
+ init_nextjs();
1105
+ init_express();
1106
+ init_cloudflare();
1080
1107
  init_types2();
1081
1108
  init_nextjs();
1082
1109
  init_express();
@@ -1838,6 +1865,13 @@ var init_symbol_table = __esm({
1838
1865
  });
1839
1866
 
1840
1867
  // src/lib/compiler/compiler.ts
1868
+ var compiler_exports = {};
1869
+ __export(compiler_exports, {
1870
+ compile: () => compile,
1871
+ compileWithPrettier: () => compileWithPrettier,
1872
+ formatWithPrettier: () => formatWithPrettier,
1873
+ traceLineToNode: () => traceLineToNode
1874
+ });
1841
1875
  import { Project } from "ts-morph";
1842
1876
  function compile(ir, options) {
1843
1877
  const pluginRegistry = createPluginRegistry();
@@ -1916,7 +1950,7 @@ function compile(ir, options) {
1916
1950
  indentSize: 2,
1917
1951
  convertTabsToSpaces: true
1918
1952
  });
1919
- const code = sourceFile.getFullText();
1953
+ let code = sourceFile.getFullText();
1920
1954
  const filePath = platform.getOutputFilePath(trigger);
1921
1955
  collectRequiredPackages(workingIR, context);
1922
1956
  const sourceMap = buildSourceMap(code, workingIR, filePath);
@@ -1933,6 +1967,28 @@ function compile(ir, options) {
1933
1967
  sourceMap
1934
1968
  };
1935
1969
  }
1970
+ async function formatWithPrettier(code, options) {
1971
+ try {
1972
+ const prettier = await import("prettier");
1973
+ return await prettier.format(code, {
1974
+ parser: "typescript",
1975
+ printWidth: options?.printWidth ?? 100,
1976
+ tabWidth: 2,
1977
+ singleQuote: options?.singleQuote ?? true,
1978
+ trailingComma: "all",
1979
+ semi: true
1980
+ });
1981
+ } catch {
1982
+ return code;
1983
+ }
1984
+ }
1985
+ async function compileWithPrettier(ir, options) {
1986
+ const result = compile(ir, options);
1987
+ if (!result.success || !result.code) return result;
1988
+ const formatted = await formatWithPrettier(result.code);
1989
+ const sourceMap = result.sourceMap ? buildSourceMap(formatted, ir, result.filePath ?? "formatted.ts") : void 0;
1990
+ return { ...result, code: formatted, sourceMap };
1991
+ }
1936
1992
  function generateCode(sourceFile, trigger, context) {
1937
1993
  const { platform } = context;
1938
1994
  platform.generateImports(sourceFile, trigger, {
@@ -2350,6 +2406,9 @@ var init_compiler = __esm({
2350
2406
  init_builtin();
2351
2407
  init_type_inference();
2352
2408
  init_symbol_table();
2409
+ registerPlatform("nextjs", () => new NextjsPlatform());
2410
+ registerPlatform("express", () => new ExpressPlatform());
2411
+ registerPlatform("cloudflare", () => new CloudflarePlatform());
2353
2412
  CONTROL_FLOW_PORT_MAP = {
2354
2413
  ["if_else" /* IF_ELSE */]: /* @__PURE__ */ new Set(["true", "false"]),
2355
2414
  ["for_loop" /* FOR_LOOP */]: /* @__PURE__ */ new Set(["body"]),
@@ -3740,7 +3799,7 @@ Return ONLY valid JSON (no markdown, no explanation). The JSON must conform to t
3740
3799
  // src/server/handlers.ts
3741
3800
  import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
3742
3801
  import { join as join2, dirname as dirname2, resolve } from "path";
3743
- function handleCompile(body, projectRoot) {
3802
+ async function handleCompile(body, projectRoot) {
3744
3803
  try {
3745
3804
  const ir = body.ir;
3746
3805
  const shouldWrite = body.write !== false;
@@ -3754,8 +3813,16 @@ function handleCompile(body, projectRoot) {
3754
3813
  body: { success: false, error: result.errors?.join("\n") }
3755
3814
  };
3756
3815
  }
3816
+ let finalCode = result.code;
3817
+ if (finalCode) {
3818
+ try {
3819
+ const { formatWithPrettier: formatWithPrettier2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
3820
+ finalCode = await formatWithPrettier2(finalCode);
3821
+ } catch {
3822
+ }
3823
+ }
3757
3824
  let writtenPath = null;
3758
- if (shouldWrite && result.filePath && result.code) {
3825
+ if (shouldWrite && result.filePath && finalCode) {
3759
3826
  const fullPath = resolve(join2(projectRoot, result.filePath));
3760
3827
  const resolvedRoot = resolve(projectRoot);
3761
3828
  const sep = resolvedRoot.endsWith("/") || resolvedRoot.endsWith("\\") ? "" : process.platform === "win32" ? "\\" : "/";
@@ -3769,7 +3836,7 @@ function handleCompile(body, projectRoot) {
3769
3836
  if (!existsSync2(dir)) {
3770
3837
  mkdirSync2(dir, { recursive: true });
3771
3838
  }
3772
- writeFileSync2(fullPath, result.code, "utf-8");
3839
+ writeFileSync2(fullPath, finalCode, "utf-8");
3773
3840
  writtenPath = fullPath;
3774
3841
  if (result.sourceMap) {
3775
3842
  const mapPath = fullPath.replace(/\.ts$/, ".flow.map.json");
@@ -3796,7 +3863,7 @@ function handleCompile(body, projectRoot) {
3796
3863
  status: 200,
3797
3864
  body: {
3798
3865
  success: true,
3799
- code: result.code,
3866
+ code: finalCode,
3800
3867
  filePath: result.filePath,
3801
3868
  writtenTo: writtenPath,
3802
3869
  dependencies: result.dependencies,
@@ -4141,7 +4208,7 @@ async function handleRequest(req, res, staticDir, projectRoot) {
4141
4208
  return;
4142
4209
  }
4143
4210
  if (pathname === "/api/compile") {
4144
- const result = handleCompile(body, projectRoot);
4211
+ const result = await handleCompile(body, projectRoot);
4145
4212
  sendJson(res, result.status, result.body);
4146
4213
  return;
4147
4214
  }
package/dist/compiler.cjs CHANGED
@@ -281,14 +281,38 @@ function validateFlowIR(ir) {
281
281
  });
282
282
  }
283
283
  }
284
+ for (const edge of workingIR.edges) {
285
+ const sourceNode = workingNodeMap.get(edge.sourceNodeId);
286
+ const targetNode = workingNodeMap.get(edge.targetNodeId);
287
+ if (!sourceNode || !targetNode) continue;
288
+ const sourcePort = sourceNode.outputs?.find((p) => p.id === edge.sourcePortId);
289
+ const targetPort = targetNode.inputs?.find((p) => p.id === edge.targetPortId);
290
+ if (!sourcePort || !targetPort) continue;
291
+ if (!isTypeCompatible(sourcePort.dataType, targetPort.dataType)) {
292
+ errors.push({
293
+ code: "TYPE_MISMATCH",
294
+ message: `Type mismatch on edge "${edge.id}": output "${sourcePort.label}" (${sourcePort.dataType}) \u2192 input "${targetPort.label}" (${targetPort.dataType})`,
295
+ edgeId: edge.id,
296
+ severity: "warning"
297
+ });
298
+ }
299
+ }
284
300
  return {
285
- valid: errors.length === 0,
301
+ valid: errors.filter((e) => e.severity !== "warning" && e.severity !== "info").length === 0,
286
302
  errors,
287
303
  migrated,
288
304
  migratedIR: migrated ? workingIR : void 0,
289
305
  migrationLog
290
306
  };
291
307
  }
308
+ function isTypeCompatible(source, target) {
309
+ if (source === target) return true;
310
+ if (source === "any" || target === "any") return true;
311
+ if (target === "object" && source === "array") return true;
312
+ if (target === "string" && source === "number") return true;
313
+ if (target === "number" && source === "string") return true;
314
+ return false;
315
+ }
292
316
  function detectCycles(nodes, edges) {
293
317
  const errors = [];
294
318
  const adjacency = /* @__PURE__ */ new Map();
@@ -1848,6 +1872,9 @@ var RESERVED_WORDS = /* @__PURE__ */ new Set([
1848
1872
  ]);
1849
1873
 
1850
1874
  // src/lib/compiler/compiler.ts
1875
+ registerPlatform("nextjs", () => new NextjsPlatform());
1876
+ registerPlatform("express", () => new ExpressPlatform());
1877
+ registerPlatform("cloudflare", () => new CloudflarePlatform());
1851
1878
  function compile(ir, options) {
1852
1879
  const pluginRegistry = createPluginRegistry();
1853
1880
  pluginRegistry.registerAll(builtinPlugins);
@@ -1925,7 +1952,7 @@ function compile(ir, options) {
1925
1952
  indentSize: 2,
1926
1953
  convertTabsToSpaces: true
1927
1954
  });
1928
- const code = sourceFile.getFullText();
1955
+ let code = sourceFile.getFullText();
1929
1956
  const filePath = platform.getOutputFilePath(trigger);
1930
1957
  collectRequiredPackages(workingIR, context);
1931
1958
  const sourceMap = buildSourceMap(code, workingIR, filePath);
@@ -3375,6 +3402,7 @@ function withFlowTrace(handler, options) {
3375
3402
  formatTraceResults(err, traces, sourceMap.generatedFile)
3376
3403
  );
3377
3404
  }
3405
+ options.onTrace?.(traces, err);
3378
3406
  }
3379
3407
  throw err;
3380
3408
  }
@@ -3383,6 +3411,7 @@ function withFlowTrace(handler, options) {
3383
3411
  }
3384
3412
  function installFlowTracer(options) {
3385
3413
  const { sourceMap, ir, editorUrl = "http://localhost:3001", log = true } = options;
3414
+ const { onTrace } = options;
3386
3415
  const handleError = (err) => {
3387
3416
  if (!(err instanceof Error)) return;
3388
3417
  const traces = traceError(err, sourceMap, ir, editorUrl);
@@ -3391,6 +3420,7 @@ function installFlowTracer(options) {
3391
3420
  formatTraceResults(err, traces, sourceMap.generatedFile)
3392
3421
  );
3393
3422
  }
3423
+ onTrace?.(traces, err);
3394
3424
  };
3395
3425
  process.on("uncaughtException", handleError);
3396
3426
  process.on("unhandledRejection", handleError);
@@ -548,6 +548,8 @@ interface CompileOptions {
548
548
  platform?: PlatformName;
549
549
  /** Additional Node Plugins */
550
550
  plugins?: NodePlugin[];
551
+ /** Format output with Prettier (default: true) */
552
+ prettier?: boolean;
551
553
  }
552
554
  /**
553
555
  * Compiles FlowIR into TypeScript source code.
@@ -598,6 +600,8 @@ interface ValidationError {
598
600
  message: string;
599
601
  nodeId?: NodeId;
600
602
  edgeId?: string;
603
+ /** Severity level (default: "error") */
604
+ severity?: "error" | "warning" | "info";
601
605
  }
602
606
  interface ValidationResult {
603
607
  valid: boolean;
@@ -909,6 +913,8 @@ interface TracerOptions {
909
913
  ir?: FlowIR;
910
914
  /** Whether to print a readable error message to console (default: true) */
911
915
  log?: boolean;
916
+ /** Callback invoked when trace results are available (push to UI store for live badges) */
917
+ onTrace?: (results: TraceResult[], error: Error) => void;
912
918
  }
913
919
  /**
914
920
  * Extract all matching line numbers from an Error object's stack trace
@@ -548,6 +548,8 @@ interface CompileOptions {
548
548
  platform?: PlatformName;
549
549
  /** Additional Node Plugins */
550
550
  plugins?: NodePlugin[];
551
+ /** Format output with Prettier (default: true) */
552
+ prettier?: boolean;
551
553
  }
552
554
  /**
553
555
  * Compiles FlowIR into TypeScript source code.
@@ -598,6 +600,8 @@ interface ValidationError {
598
600
  message: string;
599
601
  nodeId?: NodeId;
600
602
  edgeId?: string;
603
+ /** Severity level (default: "error") */
604
+ severity?: "error" | "warning" | "info";
601
605
  }
602
606
  interface ValidationResult {
603
607
  valid: boolean;
@@ -909,6 +913,8 @@ interface TracerOptions {
909
913
  ir?: FlowIR;
910
914
  /** Whether to print a readable error message to console (default: true) */
911
915
  log?: boolean;
916
+ /** Callback invoked when trace results are available (push to UI store for live badges) */
917
+ onTrace?: (results: TraceResult[], error: Error) => void;
912
918
  }
913
919
  /**
914
920
  * Extract all matching line numbers from an Error object's stack trace
package/dist/compiler.js CHANGED
@@ -214,14 +214,38 @@ function validateFlowIR(ir) {
214
214
  });
215
215
  }
216
216
  }
217
+ for (const edge of workingIR.edges) {
218
+ const sourceNode = workingNodeMap.get(edge.sourceNodeId);
219
+ const targetNode = workingNodeMap.get(edge.targetNodeId);
220
+ if (!sourceNode || !targetNode) continue;
221
+ const sourcePort = sourceNode.outputs?.find((p) => p.id === edge.sourcePortId);
222
+ const targetPort = targetNode.inputs?.find((p) => p.id === edge.targetPortId);
223
+ if (!sourcePort || !targetPort) continue;
224
+ if (!isTypeCompatible(sourcePort.dataType, targetPort.dataType)) {
225
+ errors.push({
226
+ code: "TYPE_MISMATCH",
227
+ message: `Type mismatch on edge "${edge.id}": output "${sourcePort.label}" (${sourcePort.dataType}) \u2192 input "${targetPort.label}" (${targetPort.dataType})`,
228
+ edgeId: edge.id,
229
+ severity: "warning"
230
+ });
231
+ }
232
+ }
217
233
  return {
218
- valid: errors.length === 0,
234
+ valid: errors.filter((e) => e.severity !== "warning" && e.severity !== "info").length === 0,
219
235
  errors,
220
236
  migrated,
221
237
  migratedIR: migrated ? workingIR : void 0,
222
238
  migrationLog
223
239
  };
224
240
  }
241
+ function isTypeCompatible(source, target) {
242
+ if (source === target) return true;
243
+ if (source === "any" || target === "any") return true;
244
+ if (target === "object" && source === "array") return true;
245
+ if (target === "string" && source === "number") return true;
246
+ if (target === "number" && source === "string") return true;
247
+ return false;
248
+ }
225
249
  function detectCycles(nodes, edges) {
226
250
  const errors = [];
227
251
  const adjacency = /* @__PURE__ */ new Map();
@@ -1781,6 +1805,9 @@ var RESERVED_WORDS = /* @__PURE__ */ new Set([
1781
1805
  ]);
1782
1806
 
1783
1807
  // src/lib/compiler/compiler.ts
1808
+ registerPlatform("nextjs", () => new NextjsPlatform());
1809
+ registerPlatform("express", () => new ExpressPlatform());
1810
+ registerPlatform("cloudflare", () => new CloudflarePlatform());
1784
1811
  function compile(ir, options) {
1785
1812
  const pluginRegistry = createPluginRegistry();
1786
1813
  pluginRegistry.registerAll(builtinPlugins);
@@ -1858,7 +1885,7 @@ function compile(ir, options) {
1858
1885
  indentSize: 2,
1859
1886
  convertTabsToSpaces: true
1860
1887
  });
1861
- const code = sourceFile.getFullText();
1888
+ let code = sourceFile.getFullText();
1862
1889
  const filePath = platform.getOutputFilePath(trigger);
1863
1890
  collectRequiredPackages(workingIR, context);
1864
1891
  const sourceMap = buildSourceMap(code, workingIR, filePath);
@@ -3311,6 +3338,7 @@ function withFlowTrace(handler, options) {
3311
3338
  formatTraceResults(err, traces, sourceMap.generatedFile)
3312
3339
  );
3313
3340
  }
3341
+ options.onTrace?.(traces, err);
3314
3342
  }
3315
3343
  throw err;
3316
3344
  }
@@ -3319,6 +3347,7 @@ function withFlowTrace(handler, options) {
3319
3347
  }
3320
3348
  function installFlowTracer(options) {
3321
3349
  const { sourceMap, ir, editorUrl = "http://localhost:3001", log = true } = options;
3350
+ const { onTrace } = options;
3322
3351
  const handleError = (err) => {
3323
3352
  if (!(err instanceof Error)) return;
3324
3353
  const traces = traceError(err, sourceMap, ir, editorUrl);
@@ -3327,6 +3356,7 @@ function installFlowTracer(options) {
3327
3356
  formatTraceResults(err, traces, sourceMap.generatedFile)
3328
3357
  );
3329
3358
  }
3359
+ onTrace?.(traces, err);
3330
3360
  };
3331
3361
  process.on("uncaughtException", handleError);
3332
3362
  process.on("unhandledRejection", handleError);
package/dist/server.d.ts CHANGED
@@ -21,7 +21,7 @@ export interface ServerOptions {
21
21
  onReady?: (url: string) => void;
22
22
  }
23
23
 
24
- export declare function handleCompile(body: CompileRequest, projectRoot: string): ApiResponse;
24
+ export declare function handleCompile(body: CompileRequest, projectRoot: string): Promise<ApiResponse>;
25
25
  export declare function handleGenerate(body: { prompt?: string }): Promise<ApiResponse>;
26
26
  export declare function handleImportOpenAPI(body: { spec?: unknown; filter?: { tags?: string[]; paths?: string[] } }): ApiResponse;
27
27
  export declare function startServer(options?: ServerOptions): Server;