@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.
- package/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/cli.js +5211 -0
- package/dist/compiler.cjs +4177 -0
- package/dist/compiler.d.cts +1230 -0
- package/dist/compiler.d.ts +1230 -0
- package/dist/compiler.js +4112 -0
- package/dist/server.d.ts +27 -0
- package/dist/server.js +3042 -0
- package/package.json +120 -0
|
@@ -0,0 +1,1230 @@
|
|
|
1
|
+
import { SourceFile, CodeBlockWriter } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Flow2Code Intermediate Representation (IR) Schema
|
|
5
|
+
*
|
|
6
|
+
* This is the sole contract between the visual canvas and the AST compiler.
|
|
7
|
+
* All node types, edges, and parameters are defined here.
|
|
8
|
+
*
|
|
9
|
+
* Design principles:
|
|
10
|
+
* 1. Fully decoupled from UI — IR contains no coordinate or style information
|
|
11
|
+
* 2. Type-safe — each node type has strictly typed parameters
|
|
12
|
+
* 3. Serializable — entire IR can be directly JSON.stringify/parse
|
|
13
|
+
*/
|
|
14
|
+
/** Current supported IR version number */
|
|
15
|
+
declare const CURRENT_IR_VERSION = "1.0.0";
|
|
16
|
+
/** Node unique identifier */
|
|
17
|
+
type NodeId = string;
|
|
18
|
+
/** Port identifier */
|
|
19
|
+
type PortId = string;
|
|
20
|
+
/** Supported data types */
|
|
21
|
+
type FlowDataType = "string" | "number" | "boolean" | "object" | "array" | "any" | "void" | "Response";
|
|
22
|
+
/** Input port */
|
|
23
|
+
interface InputPort {
|
|
24
|
+
id: PortId;
|
|
25
|
+
label: string;
|
|
26
|
+
dataType: FlowDataType;
|
|
27
|
+
required: boolean;
|
|
28
|
+
/** Default value (JSON serialized) */
|
|
29
|
+
defaultValue?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Output port */
|
|
32
|
+
interface OutputPort {
|
|
33
|
+
id: PortId;
|
|
34
|
+
label: string;
|
|
35
|
+
dataType: FlowDataType;
|
|
36
|
+
}
|
|
37
|
+
/** Data flow connection between nodes */
|
|
38
|
+
interface FlowEdge {
|
|
39
|
+
id: string;
|
|
40
|
+
/** Source node ID */
|
|
41
|
+
sourceNodeId: NodeId;
|
|
42
|
+
/** Source port ID */
|
|
43
|
+
sourcePortId: PortId;
|
|
44
|
+
/** Target node ID */
|
|
45
|
+
targetNodeId: NodeId;
|
|
46
|
+
/** Target port ID */
|
|
47
|
+
targetPortId: PortId;
|
|
48
|
+
}
|
|
49
|
+
declare enum NodeCategory {
|
|
50
|
+
/** Trigger: workflow entry point */
|
|
51
|
+
TRIGGER = "trigger",
|
|
52
|
+
/** Action: side-effect producing operation */
|
|
53
|
+
ACTION = "action",
|
|
54
|
+
/** Logic: branching, loops, exception handling */
|
|
55
|
+
LOGIC = "logic",
|
|
56
|
+
/** Variable: define or transform data */
|
|
57
|
+
VARIABLE = "variable",
|
|
58
|
+
/** Output: return result */
|
|
59
|
+
OUTPUT = "output"
|
|
60
|
+
}
|
|
61
|
+
declare enum TriggerType {
|
|
62
|
+
HTTP_WEBHOOK = "http_webhook",
|
|
63
|
+
CRON_JOB = "cron_job",
|
|
64
|
+
MANUAL = "manual"
|
|
65
|
+
}
|
|
66
|
+
declare enum ActionType {
|
|
67
|
+
FETCH_API = "fetch_api",
|
|
68
|
+
SQL_QUERY = "sql_query",
|
|
69
|
+
REDIS_CACHE = "redis_cache",
|
|
70
|
+
CUSTOM_CODE = "custom_code",
|
|
71
|
+
CALL_SUBFLOW = "call_subflow"
|
|
72
|
+
}
|
|
73
|
+
declare enum LogicType {
|
|
74
|
+
IF_ELSE = "if_else",
|
|
75
|
+
FOR_LOOP = "for_loop",
|
|
76
|
+
TRY_CATCH = "try_catch",
|
|
77
|
+
PROMISE_ALL = "promise_all"
|
|
78
|
+
}
|
|
79
|
+
declare enum VariableType {
|
|
80
|
+
DECLARE = "declare",
|
|
81
|
+
TRANSFORM = "transform"
|
|
82
|
+
}
|
|
83
|
+
declare enum OutputType {
|
|
84
|
+
RETURN_RESPONSE = "return_response"
|
|
85
|
+
}
|
|
86
|
+
/** HTTP Webhook trigger parameters */
|
|
87
|
+
interface HttpWebhookParams {
|
|
88
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
89
|
+
/** Route path, e.g. "/api/users" */
|
|
90
|
+
routePath: string;
|
|
91
|
+
/** Whether to parse JSON body */
|
|
92
|
+
parseBody: boolean;
|
|
93
|
+
/** Query parameter definitions */
|
|
94
|
+
queryParams?: Array<{
|
|
95
|
+
name: string;
|
|
96
|
+
type: FlowDataType;
|
|
97
|
+
required: boolean;
|
|
98
|
+
}>;
|
|
99
|
+
}
|
|
100
|
+
/** Cron Job trigger parameters */
|
|
101
|
+
interface CronJobParams {
|
|
102
|
+
/** Cron expression */
|
|
103
|
+
schedule: string;
|
|
104
|
+
/** Function name */
|
|
105
|
+
functionName: string;
|
|
106
|
+
}
|
|
107
|
+
/** Manual trigger parameters */
|
|
108
|
+
interface ManualTriggerParams {
|
|
109
|
+
/** Exported function name */
|
|
110
|
+
functionName: string;
|
|
111
|
+
/** Function parameter definitions */
|
|
112
|
+
args: Array<{
|
|
113
|
+
name: string;
|
|
114
|
+
type: FlowDataType;
|
|
115
|
+
}>;
|
|
116
|
+
}
|
|
117
|
+
/** Fetch API action parameters */
|
|
118
|
+
interface FetchApiParams {
|
|
119
|
+
/** Request URL (supports env var references like ${ENV_VAR}) */
|
|
120
|
+
url: string;
|
|
121
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
122
|
+
/** Request headers */
|
|
123
|
+
headers?: Record<string, string>;
|
|
124
|
+
/** Request body (JSON string or reference expression) */
|
|
125
|
+
body?: string;
|
|
126
|
+
/** Whether to auto-parse JSON response */
|
|
127
|
+
parseJson: boolean;
|
|
128
|
+
}
|
|
129
|
+
/** SQL Query action parameters */
|
|
130
|
+
interface SqlQueryParams {
|
|
131
|
+
/** ORM type */
|
|
132
|
+
orm: "drizzle" | "prisma" | "raw";
|
|
133
|
+
/** SQL or ORM query statement */
|
|
134
|
+
query: string;
|
|
135
|
+
/** Query parameter bindings */
|
|
136
|
+
params?: Array<{
|
|
137
|
+
name: string;
|
|
138
|
+
value: string;
|
|
139
|
+
}>;
|
|
140
|
+
}
|
|
141
|
+
/** Redis Cache action parameters */
|
|
142
|
+
interface RedisCacheParams {
|
|
143
|
+
operation: "get" | "set" | "del";
|
|
144
|
+
key: string;
|
|
145
|
+
/** Value for set operation */
|
|
146
|
+
value?: string;
|
|
147
|
+
/** TTL (seconds) */
|
|
148
|
+
ttl?: number;
|
|
149
|
+
}
|
|
150
|
+
/** Custom code parameters */
|
|
151
|
+
interface CustomCodeParams {
|
|
152
|
+
/** TypeScript code snippet */
|
|
153
|
+
code: string;
|
|
154
|
+
/** Name of the return variable */
|
|
155
|
+
returnVariable?: string;
|
|
156
|
+
/** Explicitly specify the return type (for type inference, e.g. "User[]" or "{ count: number }") */
|
|
157
|
+
returnType?: string;
|
|
158
|
+
}
|
|
159
|
+
/** Subflow call parameters */
|
|
160
|
+
interface CallSubflowParams {
|
|
161
|
+
/** Import path of the subflow (combined with compiler-generated file) */
|
|
162
|
+
flowPath: string;
|
|
163
|
+
/** Exported function name of the subflow */
|
|
164
|
+
functionName: string;
|
|
165
|
+
/** Input parameter mapping: param name → expression (supports template syntax) */
|
|
166
|
+
inputMapping: Record<string, string>;
|
|
167
|
+
/** Explicit subflow return type (optional, defaults to TypeScript inference) */
|
|
168
|
+
returnType?: string;
|
|
169
|
+
}
|
|
170
|
+
/** If/Else logic control parameters */
|
|
171
|
+
interface IfElseParams {
|
|
172
|
+
/** Condition expression (TypeScript expression string) */
|
|
173
|
+
condition: string;
|
|
174
|
+
}
|
|
175
|
+
/** For Loop logic control parameters */
|
|
176
|
+
interface ForLoopParams {
|
|
177
|
+
/** Iteration target (variable reference expression) */
|
|
178
|
+
iterableExpression: string;
|
|
179
|
+
/** Iterator variable name */
|
|
180
|
+
itemVariable: string;
|
|
181
|
+
/** Index variable name (optional) */
|
|
182
|
+
indexVariable?: string;
|
|
183
|
+
}
|
|
184
|
+
/** Try/Catch logic control parameters */
|
|
185
|
+
interface TryCatchParams {
|
|
186
|
+
/** Error variable name */
|
|
187
|
+
errorVariable: string;
|
|
188
|
+
}
|
|
189
|
+
/** Promise.all concurrency control parameters */
|
|
190
|
+
interface PromiseAllParams {
|
|
191
|
+
/** No additional params. Concurrent tasks are determined by connected child nodes */
|
|
192
|
+
[key: string]: never;
|
|
193
|
+
}
|
|
194
|
+
/** Variable declaration parameters */
|
|
195
|
+
interface DeclareVariableParams {
|
|
196
|
+
/** Variable name */
|
|
197
|
+
name: string;
|
|
198
|
+
/** Variable type */
|
|
199
|
+
dataType: FlowDataType;
|
|
200
|
+
/** Initial value expression */
|
|
201
|
+
initialValue?: string;
|
|
202
|
+
/** Whether const */
|
|
203
|
+
isConst: boolean;
|
|
204
|
+
}
|
|
205
|
+
/** Data transform parameters */
|
|
206
|
+
interface TransformParams {
|
|
207
|
+
/** Transform expression (TypeScript expression) */
|
|
208
|
+
expression: string;
|
|
209
|
+
}
|
|
210
|
+
/** Return Response parameters */
|
|
211
|
+
interface ReturnResponseParams {
|
|
212
|
+
/** HTTP status code */
|
|
213
|
+
statusCode: number;
|
|
214
|
+
/** Response body expression */
|
|
215
|
+
bodyExpression: string;
|
|
216
|
+
/** Custom response headers */
|
|
217
|
+
headers?: Record<string, string>;
|
|
218
|
+
}
|
|
219
|
+
interface NodeParamsMap {
|
|
220
|
+
[TriggerType.HTTP_WEBHOOK]: HttpWebhookParams;
|
|
221
|
+
[TriggerType.CRON_JOB]: CronJobParams;
|
|
222
|
+
[TriggerType.MANUAL]: ManualTriggerParams;
|
|
223
|
+
[ActionType.FETCH_API]: FetchApiParams;
|
|
224
|
+
[ActionType.SQL_QUERY]: SqlQueryParams;
|
|
225
|
+
[ActionType.REDIS_CACHE]: RedisCacheParams;
|
|
226
|
+
[ActionType.CUSTOM_CODE]: CustomCodeParams;
|
|
227
|
+
[ActionType.CALL_SUBFLOW]: CallSubflowParams;
|
|
228
|
+
[LogicType.IF_ELSE]: IfElseParams;
|
|
229
|
+
[LogicType.FOR_LOOP]: ForLoopParams;
|
|
230
|
+
[LogicType.TRY_CATCH]: TryCatchParams;
|
|
231
|
+
[LogicType.PROMISE_ALL]: PromiseAllParams;
|
|
232
|
+
[VariableType.DECLARE]: DeclareVariableParams;
|
|
233
|
+
[VariableType.TRANSFORM]: TransformParams;
|
|
234
|
+
[OutputType.RETURN_RESPONSE]: ReturnResponseParams;
|
|
235
|
+
}
|
|
236
|
+
type NodeType = keyof NodeParamsMap;
|
|
237
|
+
/** Generic node, params type inferred from nodeType */
|
|
238
|
+
interface FlowNode<T extends NodeType = NodeType> {
|
|
239
|
+
/** Node unique identifier */
|
|
240
|
+
id: NodeId;
|
|
241
|
+
/** Node type */
|
|
242
|
+
nodeType: T;
|
|
243
|
+
/** Category */
|
|
244
|
+
category: NodeCategory;
|
|
245
|
+
/** User-defined node label */
|
|
246
|
+
label: string;
|
|
247
|
+
/** Node parameters */
|
|
248
|
+
params: NodeParamsMap[T];
|
|
249
|
+
/** Input ports */
|
|
250
|
+
inputs: InputPort[];
|
|
251
|
+
/** Output ports */
|
|
252
|
+
outputs: OutputPort[];
|
|
253
|
+
}
|
|
254
|
+
/** Flow2Code IR Document — the sole contract between canvas and compiler */
|
|
255
|
+
interface FlowIR {
|
|
256
|
+
/** IR version */
|
|
257
|
+
version: string;
|
|
258
|
+
/** Workflow summary */
|
|
259
|
+
meta: {
|
|
260
|
+
name: string;
|
|
261
|
+
description?: string;
|
|
262
|
+
createdAt: string;
|
|
263
|
+
updatedAt: string;
|
|
264
|
+
};
|
|
265
|
+
/** All nodes */
|
|
266
|
+
nodes: FlowNode[];
|
|
267
|
+
/** All edges */
|
|
268
|
+
edges: FlowEdge[];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Flow2Code Symbol Table
|
|
273
|
+
*
|
|
274
|
+
* Maps node IDs to human-readable variable names (derived from node labels).
|
|
275
|
+
* Enables "ejectable" code generation — generated TypeScript looks like hand-written code.
|
|
276
|
+
*
|
|
277
|
+
* Naming strategy:
|
|
278
|
+
* "GET /api/hello" → getApiHello
|
|
279
|
+
* "Fetch Available Models" → fetchAvailableModels
|
|
280
|
+
* "Return Hello" → returnHello
|
|
281
|
+
* "Check Valid" → checkValid
|
|
282
|
+
* "Call External API" → callExternalApi
|
|
283
|
+
*
|
|
284
|
+
* Conflict resolution: duplicate variable names auto-suffixed with _2, _3 ...
|
|
285
|
+
* Reserved word protection: JS/TS reserved words + common identifiers in generated code auto-suffixed with Result
|
|
286
|
+
*/
|
|
287
|
+
|
|
288
|
+
interface SymbolTable {
|
|
289
|
+
/** Get the human-readable variable name for a node */
|
|
290
|
+
getVarName(nodeId: NodeId): string;
|
|
291
|
+
/** Check if a node has a named variable */
|
|
292
|
+
hasVar(nodeId: NodeId): boolean;
|
|
293
|
+
/** Get all mappings */
|
|
294
|
+
getAllMappings(): ReadonlyMap<NodeId, string>;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Platform Adapter Interface
|
|
299
|
+
*
|
|
300
|
+
* Abstracts HTTP framework implementations so the compiler is no longer coupled to Next.js.
|
|
301
|
+
* Developers can choose different target platforms to generate corresponding code.
|
|
302
|
+
*
|
|
303
|
+
* Supported platforms:
|
|
304
|
+
* - nextjs (default) Next.js App Router
|
|
305
|
+
* - express Express.js
|
|
306
|
+
* - cloudflare Cloudflare Workers
|
|
307
|
+
* - generic Generic TypeScript (pure functions, framework-agnostic)
|
|
308
|
+
*/
|
|
309
|
+
|
|
310
|
+
interface PlatformAdapter {
|
|
311
|
+
/** Platform name */
|
|
312
|
+
readonly name: string;
|
|
313
|
+
/**
|
|
314
|
+
* Generate import statements.
|
|
315
|
+
* e.g. Next.js: `import { NextResponse } from "next/server"`
|
|
316
|
+
*/
|
|
317
|
+
generateImports(sourceFile: SourceFile, trigger: FlowNode, context: PlatformContext): void;
|
|
318
|
+
/**
|
|
319
|
+
* Generate the exported main function (including signature, parameters, outer try/catch).
|
|
320
|
+
* The bodyGenerator callback is responsible for filling in the function body logic.
|
|
321
|
+
*/
|
|
322
|
+
generateFunction(sourceFile: SourceFile, trigger: FlowNode, context: PlatformContext, bodyGenerator: (writer: CodeBlockWriter) => void): void;
|
|
323
|
+
/**
|
|
324
|
+
* Generate code that returns a Response.
|
|
325
|
+
*/
|
|
326
|
+
generateResponse(writer: CodeBlockWriter, bodyExpr: string, statusCode: number, headers?: Record<string, string>): void;
|
|
327
|
+
/**
|
|
328
|
+
* Generate the error response code for the global error handler.
|
|
329
|
+
*/
|
|
330
|
+
generateErrorResponse(writer: CodeBlockWriter): void;
|
|
331
|
+
/**
|
|
332
|
+
* Generate initialization code based on trigger type (parse request body/query, etc.).
|
|
333
|
+
* Each platform uses its own HTTP API (Next.js / Express / Cloudflare Workers).
|
|
334
|
+
*/
|
|
335
|
+
generateTriggerInit(writer: CodeBlockWriter, trigger: FlowNode, context: TriggerInitContext): void;
|
|
336
|
+
/**
|
|
337
|
+
* Get the output file path.
|
|
338
|
+
*/
|
|
339
|
+
getOutputFilePath(trigger: FlowNode): string;
|
|
340
|
+
/**
|
|
341
|
+
* Implicit npm dependencies for this platform.
|
|
342
|
+
*/
|
|
343
|
+
getImplicitDependencies(): string[];
|
|
344
|
+
}
|
|
345
|
+
interface PlatformContext {
|
|
346
|
+
ir: FlowIR;
|
|
347
|
+
nodeMap: Map<NodeId, FlowNode>;
|
|
348
|
+
envVars: Set<string>;
|
|
349
|
+
imports: Map<string, Set<string>>;
|
|
350
|
+
}
|
|
351
|
+
interface TriggerInitContext {
|
|
352
|
+
symbolTable: SymbolTable;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Built-in platform names (extensible: third parties can register any string via registerPlatform).
|
|
356
|
+
*/
|
|
357
|
+
type BuiltinPlatformName = "nextjs" | "express" | "cloudflare";
|
|
358
|
+
type PlatformName = BuiltinPlatformName | (string & {});
|
|
359
|
+
/**
|
|
360
|
+
* Register a custom platform adapter.
|
|
361
|
+
*
|
|
362
|
+
* Allows users to extend the compiler's supported target platforms (e.g. Fastify, Koa, etc.).
|
|
363
|
+
* The factory function is lazily invoked on each compile to avoid global state pollution.
|
|
364
|
+
*
|
|
365
|
+
* @param name - Platform name, can use a built-in name or a custom string
|
|
366
|
+
* @param factory - Platform adapter factory function
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```ts
|
|
370
|
+
* import { registerPlatform } from "flow2code";
|
|
371
|
+
*
|
|
372
|
+
* registerPlatform("fastify", () => ({
|
|
373
|
+
* imports: () => ['import Fastify from "fastify";'],
|
|
374
|
+
* routeWrapper: (method, path, body) => `app.${method}("${path}", async (req, reply) => {\n${body}\n});`,
|
|
375
|
+
* response: (statusExpr, bodyExpr) => `reply.status(${statusExpr}).send(${bodyExpr});`,
|
|
376
|
+
* appSetup: () => 'const app = Fastify();',
|
|
377
|
+
* listen: (port) => `app.listen({ port: ${port} });`,
|
|
378
|
+
* }));
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
declare function registerPlatform(name: PlatformName, factory: () => PlatformAdapter): void;
|
|
382
|
+
declare function getPlatform(name: PlatformName): PlatformAdapter;
|
|
383
|
+
declare function getAvailablePlatforms(): string[];
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Node Plugin Interface
|
|
387
|
+
*
|
|
388
|
+
* Allows developers to register custom node code generators without modifying compiler source code.
|
|
389
|
+
*
|
|
390
|
+
* Usage example:
|
|
391
|
+
* ```ts
|
|
392
|
+
* import { registerPlugin } from "flow2code/compiler";
|
|
393
|
+
*
|
|
394
|
+
* registerPlugin({
|
|
395
|
+
* nodeType: "aws_ses_email",
|
|
396
|
+
* generate(node, writer, context) {
|
|
397
|
+
* writer.writeLine(`await ses.sendEmail({ ... });`);
|
|
398
|
+
* writer.writeLine(`flowState['${node.id}'] = { sent: true };`);
|
|
399
|
+
* },
|
|
400
|
+
* getRequiredPackages: () => ["@aws-sdk/client-ses"],
|
|
401
|
+
* getOutputType: () => "{ sent: boolean }",
|
|
402
|
+
* });
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
|
|
406
|
+
interface PluginContext {
|
|
407
|
+
ir: FlowIR;
|
|
408
|
+
nodeMap: Map<NodeId, FlowNode>;
|
|
409
|
+
envVars: Set<string>;
|
|
410
|
+
imports: Map<string, Set<string>>;
|
|
411
|
+
requiredPackages: Set<string>;
|
|
412
|
+
/** Resolve template expressions (automatically uses expression parser) */
|
|
413
|
+
resolveExpression: (expr: string, currentNodeId?: NodeId) => string;
|
|
414
|
+
/** Resolve environment variable references */
|
|
415
|
+
resolveEnvVars: (url: string) => string;
|
|
416
|
+
/** Generate child node code (used for branching nodes like if/else, try/catch) */
|
|
417
|
+
generateChildNode: (writer: CodeBlockWriter, node: FlowNode) => void;
|
|
418
|
+
/** Get the human-readable variable name for a node (provided by Symbol Table) */
|
|
419
|
+
getVarName: (nodeId: NodeId) => string;
|
|
420
|
+
/**
|
|
421
|
+
* Push a local scope layer.
|
|
422
|
+
* Within this scope, all expression references to nodeId will resolve to scopeVar.
|
|
423
|
+
*/
|
|
424
|
+
pushScope: (nodeId: NodeId, scopeVar: string) => void;
|
|
425
|
+
/**
|
|
426
|
+
* Pop the innermost local scope.
|
|
427
|
+
*/
|
|
428
|
+
popScope: () => void;
|
|
429
|
+
}
|
|
430
|
+
interface NodePlugin {
|
|
431
|
+
/** Node type identifier (corresponds to FlowNode.nodeType) */
|
|
432
|
+
readonly nodeType: string;
|
|
433
|
+
/**
|
|
434
|
+
* Generate TypeScript code for this node.
|
|
435
|
+
*/
|
|
436
|
+
generate(node: FlowNode, writer: CodeBlockWriter, context: PluginContext): void;
|
|
437
|
+
/**
|
|
438
|
+
* Declare npm packages required by this node.
|
|
439
|
+
* @returns Array of package names, e.g. ["@aws-sdk/client-ses"]
|
|
440
|
+
*/
|
|
441
|
+
getRequiredPackages?(node: FlowNode): string[];
|
|
442
|
+
/**
|
|
443
|
+
* Infer the TypeScript type of this node's output value.
|
|
444
|
+
* Used to generate a typed flowState interface.
|
|
445
|
+
* @returns TypeScript type string, e.g. "{ sent: boolean }"
|
|
446
|
+
*/
|
|
447
|
+
getOutputType?(node: FlowNode): string;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Plugin Registry instance.
|
|
451
|
+
* Each `compile()` call can create an independent registry to avoid global state pollution.
|
|
452
|
+
*/
|
|
453
|
+
interface PluginRegistry {
|
|
454
|
+
register(plugin: NodePlugin): void;
|
|
455
|
+
registerAll(plugins: NodePlugin[]): void;
|
|
456
|
+
get(nodeType: string): NodePlugin | undefined;
|
|
457
|
+
has(nodeType: string): boolean;
|
|
458
|
+
getAll(): Map<string, NodePlugin>;
|
|
459
|
+
clear(): void;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Create a new Plugin Registry instance.
|
|
463
|
+
*/
|
|
464
|
+
declare function createPluginRegistry(): PluginRegistry;
|
|
465
|
+
/**
|
|
466
|
+
* Register a node plugin (global).
|
|
467
|
+
* If a plugin with the same name already exists, it will be overwritten (allows developers to replace built-in plugins).
|
|
468
|
+
*/
|
|
469
|
+
declare function registerPlugin(plugin: NodePlugin): void;
|
|
470
|
+
/**
|
|
471
|
+
* Batch-register multiple plugins (global).
|
|
472
|
+
*/
|
|
473
|
+
declare function registerPlugins(plugins: NodePlugin[]): void;
|
|
474
|
+
/**
|
|
475
|
+
* Get the plugin for a specific node type (global).
|
|
476
|
+
*/
|
|
477
|
+
declare function getPlugin(nodeType: string): NodePlugin | undefined;
|
|
478
|
+
/**
|
|
479
|
+
* Get all registered plugins (global).
|
|
480
|
+
*/
|
|
481
|
+
declare function getAllPlugins(): Map<string, NodePlugin>;
|
|
482
|
+
/**
|
|
483
|
+
* Clear all registered plugins (global, for testing).
|
|
484
|
+
*/
|
|
485
|
+
declare function clearPlugins(): void;
|
|
486
|
+
/**
|
|
487
|
+
* Check if a specific node type is registered (global).
|
|
488
|
+
*/
|
|
489
|
+
declare function hasPlugin(nodeType: string): boolean;
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Built-in Node Plugins
|
|
493
|
+
*
|
|
494
|
+
* All built-in node code generators extracted from the compiler core.
|
|
495
|
+
* Each generator follows the NodePlugin interface and can be overridden by external plugins.
|
|
496
|
+
*/
|
|
497
|
+
|
|
498
|
+
declare const builtinPlugins: NodePlugin[];
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Flow2Code AST Compiler Core (v2)
|
|
502
|
+
*
|
|
503
|
+
* Architecture:
|
|
504
|
+
* 1. Platform Adapter — Decoupled from Next.js, supports Express / Cloudflare Workers
|
|
505
|
+
* 2. Plugin System — Node generation logic is externally registerable
|
|
506
|
+
* 3. Expression Parser — Recursive Descent Parser replaces regex
|
|
507
|
+
* 4. Type Inference — Generates typed FlowState interface instead of Record<string, any>
|
|
508
|
+
* 5. Scoped State — Loops/try-catch use local scope to prevent variable shadowing
|
|
509
|
+
*
|
|
510
|
+
* Public API (backward-compatible):
|
|
511
|
+
* - compile(ir) → CompileResult (default: nextjs platform)
|
|
512
|
+
* - compile(ir, { platform }) → CompileResult (specified platform)
|
|
513
|
+
* - traceLineToNode(sourceMap, line) → Reverse lookup node from line number
|
|
514
|
+
*/
|
|
515
|
+
|
|
516
|
+
interface CompileResult {
|
|
517
|
+
success: boolean;
|
|
518
|
+
code?: string;
|
|
519
|
+
errors?: string[];
|
|
520
|
+
/** Generated file path (relative) */
|
|
521
|
+
filePath?: string;
|
|
522
|
+
/** Dependency report */
|
|
523
|
+
dependencies?: DependencyReport;
|
|
524
|
+
/** Source Map (nodeId ↔ line number mapping) */
|
|
525
|
+
sourceMap?: SourceMap;
|
|
526
|
+
}
|
|
527
|
+
/** Dependency report */
|
|
528
|
+
interface DependencyReport {
|
|
529
|
+
/** All required packages */
|
|
530
|
+
all: string[];
|
|
531
|
+
/** Missing packages (compared with package.json) */
|
|
532
|
+
missing: string[];
|
|
533
|
+
/** Suggested install command */
|
|
534
|
+
installCommand?: string;
|
|
535
|
+
}
|
|
536
|
+
/** Source Map: line number ↔ node ID mapping */
|
|
537
|
+
interface SourceMap {
|
|
538
|
+
version: 1;
|
|
539
|
+
generatedFile: string;
|
|
540
|
+
/** nodeId → { startLine, endLine } */
|
|
541
|
+
mappings: Record<string, {
|
|
542
|
+
startLine: number;
|
|
543
|
+
endLine: number;
|
|
544
|
+
}>;
|
|
545
|
+
}
|
|
546
|
+
interface CompileOptions {
|
|
547
|
+
/** Target platform (default: "nextjs") */
|
|
548
|
+
platform?: PlatformName;
|
|
549
|
+
/** Additional Node Plugins */
|
|
550
|
+
plugins?: NodePlugin[];
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Compiles FlowIR into TypeScript source code.
|
|
554
|
+
*
|
|
555
|
+
* Pipeline stages:
|
|
556
|
+
* 1. Validate IR structure
|
|
557
|
+
* 2. Topological sort + concurrency detection
|
|
558
|
+
* 3. Platform adaptation (Next.js / Express / Cloudflare etc.)
|
|
559
|
+
* 4. Plugin-based node code generation
|
|
560
|
+
* 5. IDE-friendly Source Map output
|
|
561
|
+
*
|
|
562
|
+
* @param ir - FlowIR input document
|
|
563
|
+
* @param options - Compile options (platform, plugins, output path etc.)
|
|
564
|
+
* @returns Compile result containing code, filePath, sourceMap, dependencies
|
|
565
|
+
*
|
|
566
|
+
* @example
|
|
567
|
+
* ```ts
|
|
568
|
+
* import { compile } from "flow2code";
|
|
569
|
+
*
|
|
570
|
+
* const result = compile(ir, { platform: "hono" });
|
|
571
|
+
* if (result.success) {
|
|
572
|
+
* console.log(result.code);
|
|
573
|
+
* }
|
|
574
|
+
* ```
|
|
575
|
+
*/
|
|
576
|
+
declare function compile(ir: FlowIR, options?: CompileOptions): CompileResult;
|
|
577
|
+
/**
|
|
578
|
+
* Given a line number, reverse-lookup the corresponding nodeId.
|
|
579
|
+
*/
|
|
580
|
+
declare function traceLineToNode(sourceMap: SourceMap, line: number): {
|
|
581
|
+
nodeId: string;
|
|
582
|
+
startLine: number;
|
|
583
|
+
endLine: number;
|
|
584
|
+
} | null;
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Flow IR Validator
|
|
588
|
+
*
|
|
589
|
+
* Validates structural correctness of an IR document before compilation:
|
|
590
|
+
* 1. Must have exactly one Trigger node
|
|
591
|
+
* 2. All Edge source/target must reference existing nodes
|
|
592
|
+
* 3. No orphan nodes (except Trigger)
|
|
593
|
+
* 4. Graph must be a DAG (no cycles)
|
|
594
|
+
*/
|
|
595
|
+
|
|
596
|
+
interface ValidationError {
|
|
597
|
+
code: string;
|
|
598
|
+
message: string;
|
|
599
|
+
nodeId?: NodeId;
|
|
600
|
+
edgeId?: string;
|
|
601
|
+
}
|
|
602
|
+
interface ValidationResult {
|
|
603
|
+
valid: boolean;
|
|
604
|
+
errors: ValidationError[];
|
|
605
|
+
/** If the IR was auto-migrated, records the migration path */
|
|
606
|
+
migrated?: boolean;
|
|
607
|
+
migratedIR?: FlowIR;
|
|
608
|
+
migrationLog?: string[];
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Validates the structural correctness of a FlowIR document.
|
|
612
|
+
* Auto-migrates older versions before validation if needed.
|
|
613
|
+
*/
|
|
614
|
+
declare function validateFlowIR(ir: FlowIR): ValidationResult;
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* AI-Generated IR Security Check
|
|
618
|
+
*
|
|
619
|
+
* When IR is produced by AI/LLM, custom_code nodes may contain malicious code.
|
|
620
|
+
* This module intercepts at the "load/import" stage, earlier than the compile-time `DANGEROUS_CODE_PATTERNS`.
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* ```ts
|
|
624
|
+
* import { validateIRSecurity } from "flow2code/compiler";
|
|
625
|
+
*
|
|
626
|
+
* const result = validateIRSecurity(aiGeneratedIR);
|
|
627
|
+
* if (!result.safe) {
|
|
628
|
+
* console.warn("⚠️ Dangerous patterns:", result.findings);
|
|
629
|
+
* }
|
|
630
|
+
* ```
|
|
631
|
+
*/
|
|
632
|
+
|
|
633
|
+
interface SecurityFinding {
|
|
634
|
+
/** Severity level */
|
|
635
|
+
severity: "critical" | "warning" | "info";
|
|
636
|
+
/** Node ID */
|
|
637
|
+
nodeId: NodeId;
|
|
638
|
+
/** Node label */
|
|
639
|
+
nodeLabel: string;
|
|
640
|
+
/** Description of the detected pattern */
|
|
641
|
+
pattern: string;
|
|
642
|
+
/** Matched code snippet (truncated to 80 characters) */
|
|
643
|
+
match: string;
|
|
644
|
+
}
|
|
645
|
+
interface SecurityCheckResult {
|
|
646
|
+
/** Whether it is safe (no critical findings) */
|
|
647
|
+
safe: boolean;
|
|
648
|
+
/** All detection results */
|
|
649
|
+
findings: SecurityFinding[];
|
|
650
|
+
/** Number of nodes scanned */
|
|
651
|
+
nodesScanned: number;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Validate the security of all nodes in a FlowIR
|
|
655
|
+
*
|
|
656
|
+
* Specifically targets AI/LLM-generated IR, detecting malicious patterns at the load/import stage.
|
|
657
|
+
* Scan scope: custom_code, transform, if_else, return_response, fetch_api, and all other fields containing code/expressions.
|
|
658
|
+
*
|
|
659
|
+
* @param ir - The FlowIR to check
|
|
660
|
+
* @returns SecurityCheckResult - Contains safety verdict, all detection findings, and scan statistics
|
|
661
|
+
*/
|
|
662
|
+
declare function validateIRSecurity(ir: FlowIR): SecurityCheckResult;
|
|
663
|
+
/**
|
|
664
|
+
* Format security check results into a human-readable string
|
|
665
|
+
*/
|
|
666
|
+
declare function formatSecurityReport(result: SecurityCheckResult): string;
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Topological Sort and Concurrency Detection Algorithm
|
|
670
|
+
*
|
|
671
|
+
* Features:
|
|
672
|
+
* 1. Compute node execution priority (topological sort)
|
|
673
|
+
* 2. Detect groups of concurrently executable nodes (for generating Promise.all)
|
|
674
|
+
* 3. Build Execution Plan
|
|
675
|
+
*/
|
|
676
|
+
|
|
677
|
+
/** Single execution step */
|
|
678
|
+
interface ExecutionStep {
|
|
679
|
+
/** Step index */
|
|
680
|
+
index: number;
|
|
681
|
+
/**
|
|
682
|
+
* List of node IDs to execute in this step.
|
|
683
|
+
* If length > 1, they can be executed concurrently with Promise.all.
|
|
684
|
+
*/
|
|
685
|
+
nodeIds: NodeId[];
|
|
686
|
+
/** Whether it can execute concurrently */
|
|
687
|
+
concurrent: boolean;
|
|
688
|
+
}
|
|
689
|
+
/** Complete execution plan */
|
|
690
|
+
interface ExecutionPlan {
|
|
691
|
+
/** All steps (topologically sorted) */
|
|
692
|
+
steps: ExecutionStep[];
|
|
693
|
+
/** Topological sort result (flat) */
|
|
694
|
+
sortedNodeIds: NodeId[];
|
|
695
|
+
/** Incoming nodes for each node (dependency sources) */
|
|
696
|
+
dependencies: Map<NodeId, Set<NodeId>>;
|
|
697
|
+
/** Outgoing nodes for each node (dependency targets) */
|
|
698
|
+
dependents: Map<NodeId, Set<NodeId>>;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Perform topological sort using Kahn's algorithm,
|
|
702
|
+
* grouping nodes at the same level (indegree reaches zero simultaneously)
|
|
703
|
+
* to detect concurrently executable nodes.
|
|
704
|
+
*/
|
|
705
|
+
declare function topologicalSort(ir: FlowIR): ExecutionPlan;
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Flow2Code Expression Parser
|
|
709
|
+
*
|
|
710
|
+
* Replaces fragile Regex parsing with a Recursive Descent Parser for template expressions.
|
|
711
|
+
*
|
|
712
|
+
* Supported syntax:
|
|
713
|
+
* {{nodeId}} → flowState['nodeId']
|
|
714
|
+
* {{nodeId.path.to.value}} → flowState['nodeId'].path.to.value
|
|
715
|
+
* {{nodeId.arr[0].name}} → flowState['nodeId'].arr[0].name
|
|
716
|
+
* {{$input}} → auto-resolves upstream non-trigger node
|
|
717
|
+
* {{$input.data.items}} → same, with sub-path
|
|
718
|
+
* {{$trigger}} → trigger node's flowState
|
|
719
|
+
* {{$trigger.body.userId}} → flowState['triggerId'].body.userId
|
|
720
|
+
*
|
|
721
|
+
* Differences from Regex version:
|
|
722
|
+
* - Correctly handles nested brackets e.g. {{node.arr[items[0]]}}
|
|
723
|
+
* - Correctly handles whitespace e.g. {{ $input.data }}
|
|
724
|
+
* - Clear error messages, no silent fallthrough
|
|
725
|
+
* - Supports escape sequences \\{{ to output literal {{
|
|
726
|
+
*/
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Scope Entry: describes the current scope during code generation.
|
|
730
|
+
* For example, inside a for-loop, the loop node's ID should resolve
|
|
731
|
+
* to _loopScope instead of flowState.
|
|
732
|
+
*/
|
|
733
|
+
interface ScopeEntry {
|
|
734
|
+
/** Node ID mapped by this scope */
|
|
735
|
+
nodeId: NodeId;
|
|
736
|
+
/** Variable name in generated code (e.g. _loopScope) */
|
|
737
|
+
scopeVar: string;
|
|
738
|
+
}
|
|
739
|
+
interface ExpressionContext {
|
|
740
|
+
/** Current IR */
|
|
741
|
+
ir: FlowIR;
|
|
742
|
+
/** Node ID → FlowNode mapping */
|
|
743
|
+
nodeMap: Map<NodeId, FlowNode>;
|
|
744
|
+
/** Current node ID (used for resolving $input) */
|
|
745
|
+
currentNodeId?: NodeId;
|
|
746
|
+
/** Symbol Table: when enabled, expressions use named variables instead of flowState['nodeId'] */
|
|
747
|
+
symbolTable?: SymbolTable;
|
|
748
|
+
/**
|
|
749
|
+
* Scope Stack: outermost to innermost scope entries.
|
|
750
|
+
* When a reference's base matches a scope's nodeId,
|
|
751
|
+
* it resolves to scopeVar['nodeId'] instead of flowState['nodeId'].
|
|
752
|
+
*/
|
|
753
|
+
scopeStack?: ScopeEntry[];
|
|
754
|
+
/**
|
|
755
|
+
* Block-scoped node IDs: these nodes are generated inside sub-blocks
|
|
756
|
+
* (if/else, try/catch, for-loop body), and their Symbol Table aliases
|
|
757
|
+
* are not visible in the outer scope.
|
|
758
|
+
* Expression parsing must fallback to flowState['nodeId'].
|
|
759
|
+
*/
|
|
760
|
+
blockScopedNodeIds?: Set<NodeId>;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Parses all {{...}} references in an expression string and replaces them
|
|
764
|
+
* with the corresponding flowState access expressions.
|
|
765
|
+
*
|
|
766
|
+
* @param expr - Raw expression (may contain {{...}} template syntax)
|
|
767
|
+
* @param context - Compiler context
|
|
768
|
+
* @returns Parsed TypeScript expression
|
|
769
|
+
*/
|
|
770
|
+
declare function parseExpression(expr: string, context: ExpressionContext): string;
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Flow2Code Type Inference Engine
|
|
774
|
+
*
|
|
775
|
+
* Infers TypeScript types for flowState based on FlowIR node definitions and port types.
|
|
776
|
+
* Replaces the crude `Record<string, any>` declaration to provide real type safety in generated code.
|
|
777
|
+
*
|
|
778
|
+
* Inference strategies:
|
|
779
|
+
* 1. Trigger nodes: inferred from trigger type and parameters (e.g. HTTP body, query, etc.)
|
|
780
|
+
* 2. Action nodes: inferred from the plugin's getOutputType()
|
|
781
|
+
* 3. Manual specification: uses the port's dataType as a fallback
|
|
782
|
+
* 4. Auto-narrowing: $input references are narrowed based on upstream node types
|
|
783
|
+
*/
|
|
784
|
+
|
|
785
|
+
interface FlowStateTypeInfo {
|
|
786
|
+
/** Complete TypeScript interface source code */
|
|
787
|
+
interfaceCode: string;
|
|
788
|
+
/** TypeScript type corresponding to each node ID */
|
|
789
|
+
nodeTypes: Map<string, string>;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Infer the output types of all nodes in a FlowIR and generate the corresponding TypeScript interface.
|
|
793
|
+
*
|
|
794
|
+
* @param ir - Flow IR
|
|
795
|
+
* @returns FlowStateTypeInfo containing the generated interface and per-node type mappings
|
|
796
|
+
*/
|
|
797
|
+
declare function inferFlowStateTypes(ir: FlowIR, registry?: PluginRegistry): FlowStateTypeInfo;
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Flow2Code Universal Decompiler — TypeScript → FlowIR
|
|
801
|
+
*
|
|
802
|
+
* Reverse-parses **any** TypeScript source code into FlowIR intermediate representation.
|
|
803
|
+
* Designed for AI Code Audit: accepts AI-generated TypeScript and visualizes its logic flow.
|
|
804
|
+
*
|
|
805
|
+
* Strategy:
|
|
806
|
+
* 1. AST Analysis (primary): ts-morph parses TS AST, pattern-matches known constructs
|
|
807
|
+
* 2. Data Flow Tracking: variable def-use chains build real edges (not linear chaining)
|
|
808
|
+
* 3. Source Map Hints (optional): if `// --- label [nodeType] [nodeId] ---` markers exist,
|
|
809
|
+
* they enhance node labeling but are NOT required
|
|
810
|
+
*
|
|
811
|
+
* Supported patterns:
|
|
812
|
+
* - Export functions / arrow functions / class methods → Triggers
|
|
813
|
+
* - `await fetch(...)` → Fetch API nodes
|
|
814
|
+
* - `if/else` → If/Else logic nodes
|
|
815
|
+
* - `for...of` / `for...in` / `for` → Loop nodes
|
|
816
|
+
* - `try/catch` → TryCatch nodes
|
|
817
|
+
* - Variable declarations → Variable nodes
|
|
818
|
+
* - Generic `await expr` → Custom code action nodes
|
|
819
|
+
* - Return / Response statements → Output nodes
|
|
820
|
+
*
|
|
821
|
+
* Edge building:
|
|
822
|
+
* - Data flow edges: tracks which variables are produced and consumed by each node
|
|
823
|
+
* - Control flow edges: if/else branches, loop bodies, try/catch blocks
|
|
824
|
+
* - Sequential edges: statements in the same scope with no explicit data dependency
|
|
825
|
+
*/
|
|
826
|
+
|
|
827
|
+
interface DecompileResult {
|
|
828
|
+
success: boolean;
|
|
829
|
+
ir?: FlowIR;
|
|
830
|
+
errors?: string[];
|
|
831
|
+
/** Confidence score 0-1 indicating decompilation accuracy */
|
|
832
|
+
confidence: number;
|
|
833
|
+
/** Audit hints: detected issues or notable patterns */
|
|
834
|
+
audit?: AuditHint[];
|
|
835
|
+
}
|
|
836
|
+
/** Audit hint: a detected issue or notable pattern in the code */
|
|
837
|
+
interface AuditHint {
|
|
838
|
+
nodeId: string;
|
|
839
|
+
severity: "info" | "warning" | "error";
|
|
840
|
+
message: string;
|
|
841
|
+
/** Source line number (1-indexed) */
|
|
842
|
+
line?: number;
|
|
843
|
+
}
|
|
844
|
+
interface DecompileOptions {
|
|
845
|
+
/** File name or full file path (used for route inference) */
|
|
846
|
+
fileName?: string;
|
|
847
|
+
/** Target function name to decompile (omit to auto-detect) */
|
|
848
|
+
functionName?: string;
|
|
849
|
+
/** Enable audit hints (default: true) */
|
|
850
|
+
audit?: boolean;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Decompiles any TypeScript source code into FlowIR.
|
|
854
|
+
*
|
|
855
|
+
* @param code - TypeScript source code (arbitrary, not limited to flow2code output)
|
|
856
|
+
* @param options - Optional settings
|
|
857
|
+
* @returns DecompileResult with IR, confidence, and optional audit hints
|
|
858
|
+
*/
|
|
859
|
+
declare function decompile(code: string, options?: DecompileOptions): DecompileResult;
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Flow2Code Runtime Error Tracer
|
|
863
|
+
*
|
|
864
|
+
* Solves the "debugging experience gap" problem:
|
|
865
|
+
* When a compiled API Route throws a runtime error,
|
|
866
|
+
* it automatically intercepts the error stack, reverse-lookups the Source Map,
|
|
867
|
+
* and prints a clickable deep link to jump directly to the error node on the Flow2Code canvas.
|
|
868
|
+
*
|
|
869
|
+
* Usage:
|
|
870
|
+
* ```ts
|
|
871
|
+
* // 1. Next.js middleware / wrap handler
|
|
872
|
+
* import { withFlowTrace } from "flow2code/compiler";
|
|
873
|
+
*
|
|
874
|
+
* export const GET = withFlowTrace(handler, { flowFile: "my-flow.flow.json" });
|
|
875
|
+
*
|
|
876
|
+
* // 2. Global install (intercept uncaught errors)
|
|
877
|
+
* import { installFlowTracer } from "flow2code/compiler";
|
|
878
|
+
*
|
|
879
|
+
* installFlowTracer({ port: 3001 });
|
|
880
|
+
* ```
|
|
881
|
+
*
|
|
882
|
+
* Output example:
|
|
883
|
+
* ```
|
|
884
|
+
* ❌ Runtime Error in generated.ts:45
|
|
885
|
+
* → Flow2Code Node: [fetch_api_1] "Fetch User Data"
|
|
886
|
+
* → 🔗 http://localhost:3001?highlight=fetch_api_1
|
|
887
|
+
* ```
|
|
888
|
+
*/
|
|
889
|
+
|
|
890
|
+
interface TraceResult {
|
|
891
|
+
/** Matched node ID */
|
|
892
|
+
nodeId: string;
|
|
893
|
+
/** Node label */
|
|
894
|
+
nodeLabel: string;
|
|
895
|
+
/** Node type */
|
|
896
|
+
nodeType: string;
|
|
897
|
+
/** Source code line number range */
|
|
898
|
+
startLine: number;
|
|
899
|
+
endLine: number;
|
|
900
|
+
/** Clickable deep link (opens canvas and highlights node) */
|
|
901
|
+
deepLink: string;
|
|
902
|
+
}
|
|
903
|
+
interface TracerOptions {
|
|
904
|
+
/** Flow2Code editor URL (default: http://localhost:3001) */
|
|
905
|
+
editorUrl?: string;
|
|
906
|
+
/** Source Map object (if already in memory) */
|
|
907
|
+
sourceMap?: SourceMap;
|
|
908
|
+
/** FlowIR object (used to retrieve node labels) */
|
|
909
|
+
ir?: FlowIR;
|
|
910
|
+
/** Whether to print a readable error message to console (default: true) */
|
|
911
|
+
log?: boolean;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Extract all matching line numbers from an Error object's stack trace
|
|
915
|
+
* and reverse-lookup the corresponding Flow nodes via Source Map.
|
|
916
|
+
*
|
|
917
|
+
* @param error - Original Error object
|
|
918
|
+
* @param sourceMap - Source Map generated at compile time
|
|
919
|
+
* @param ir - FlowIR (used to retrieve label / nodeType)
|
|
920
|
+
* @param editorUrl - Editor URL
|
|
921
|
+
* @returns All matching TraceResults (in stack order)
|
|
922
|
+
*/
|
|
923
|
+
declare function traceError(error: Error, sourceMap: SourceMap, ir?: FlowIR, editorUrl?: string): TraceResult[];
|
|
924
|
+
/**
|
|
925
|
+
* Format TraceResults into a readable console message.
|
|
926
|
+
*/
|
|
927
|
+
declare function formatTraceResults(error: Error, traces: TraceResult[], generatedFile: string): string;
|
|
928
|
+
type AsyncHandler = (...args: unknown[]) => Promise<unknown>;
|
|
929
|
+
/**
|
|
930
|
+
* Wrap an API handler to automatically intercept runtime errors and print Flow node traces.
|
|
931
|
+
*
|
|
932
|
+
* @example
|
|
933
|
+
* ```ts
|
|
934
|
+
* import { withFlowTrace } from "flow2code/compiler";
|
|
935
|
+
* import sourceMap from "./my-flow.flow.map.json";
|
|
936
|
+
*
|
|
937
|
+
* async function handler(req: Request) {
|
|
938
|
+
* // ... generated code
|
|
939
|
+
* }
|
|
940
|
+
*
|
|
941
|
+
* export const GET = withFlowTrace(handler, { sourceMap });
|
|
942
|
+
* ```
|
|
943
|
+
*/
|
|
944
|
+
declare function withFlowTrace<T extends AsyncHandler>(handler: T, options: TracerOptions): T;
|
|
945
|
+
/**
|
|
946
|
+
* Install the Flow2Code error tracer at the process level.
|
|
947
|
+
* Listens for `uncaughtException` and `unhandledRejection`,
|
|
948
|
+
* automatically printing deep links for errors matching the Source Map.
|
|
949
|
+
*
|
|
950
|
+
* @param options - TracerOptions (must include sourceMap)
|
|
951
|
+
* @returns Cleanup function (uninstall tracer)
|
|
952
|
+
*
|
|
953
|
+
* @example
|
|
954
|
+
* ```ts
|
|
955
|
+
* import { installFlowTracer } from "flow2code/compiler";
|
|
956
|
+
* import sourceMap from "./my-flow.flow.map.json";
|
|
957
|
+
*
|
|
958
|
+
* const uninstall = installFlowTracer({ sourceMap, editorUrl: "http://localhost:3001" });
|
|
959
|
+
*
|
|
960
|
+
* // Later, stop tracing
|
|
961
|
+
* uninstall();
|
|
962
|
+
* ```
|
|
963
|
+
*/
|
|
964
|
+
declare function installFlowTracer(options: TracerOptions & {
|
|
965
|
+
sourceMap: SourceMap;
|
|
966
|
+
}): () => void;
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Flow2Code Dynamic Node Registry
|
|
970
|
+
*
|
|
971
|
+
* Solves the "core and nodes over-coupling" problem:
|
|
972
|
+
* Node definitions are no longer hardcoded in switch statements,
|
|
973
|
+
* but independently described via `NodeDefinition` and dynamically registered.
|
|
974
|
+
*
|
|
975
|
+
* Community extensions simply need to:
|
|
976
|
+
* ```ts
|
|
977
|
+
* import { nodeRegistry } from "flow2code/compiler";
|
|
978
|
+
*
|
|
979
|
+
* nodeRegistry.register({
|
|
980
|
+
* nodeType: "action:s3_upload",
|
|
981
|
+
* category: NodeCategory.ACTION,
|
|
982
|
+
* label: "AWS S3 Upload",
|
|
983
|
+
* icon: "☁️",
|
|
984
|
+
* description: "Upload file to AWS S3 Bucket",
|
|
985
|
+
* defaultPorts: {
|
|
986
|
+
* inputs: [{ id: "file", label: "File", dataType: "object", required: true }],
|
|
987
|
+
* outputs: [{ id: "url", label: "URL", dataType: "string" }],
|
|
988
|
+
* },
|
|
989
|
+
* defaultParams: { bucket: "", region: "us-east-1" },
|
|
990
|
+
* });
|
|
991
|
+
* ```
|
|
992
|
+
*/
|
|
993
|
+
|
|
994
|
+
interface NodeDefinition {
|
|
995
|
+
/** Node type identifier (corresponds to IR's nodeType) */
|
|
996
|
+
nodeType: string;
|
|
997
|
+
/** Node category */
|
|
998
|
+
category: NodeCategory;
|
|
999
|
+
/** Display label */
|
|
1000
|
+
label: string;
|
|
1001
|
+
/** Emoji / icon (for NodeLibrary) */
|
|
1002
|
+
icon: string;
|
|
1003
|
+
/** Node description (tooltip) */
|
|
1004
|
+
description?: string;
|
|
1005
|
+
/** Default ports */
|
|
1006
|
+
defaultPorts: {
|
|
1007
|
+
inputs: InputPort[];
|
|
1008
|
+
outputs: OutputPort[];
|
|
1009
|
+
};
|
|
1010
|
+
/** Default parameters */
|
|
1011
|
+
defaultParams: Record<string, unknown>;
|
|
1012
|
+
/** Group name displayed in NodeLibrary; defaults to category group if omitted */
|
|
1013
|
+
group?: string;
|
|
1014
|
+
/** Sort weight (lower = higher priority), default 100 */
|
|
1015
|
+
order?: number;
|
|
1016
|
+
}
|
|
1017
|
+
declare class NodeRegistry {
|
|
1018
|
+
private definitions;
|
|
1019
|
+
/** Register a single node definition */
|
|
1020
|
+
register(def: NodeDefinition): void;
|
|
1021
|
+
/** Batch register */
|
|
1022
|
+
registerAll(defs: NodeDefinition[]): void;
|
|
1023
|
+
/** Get a single node definition */
|
|
1024
|
+
get(nodeType: string): NodeDefinition | undefined;
|
|
1025
|
+
/** Get all node definitions */
|
|
1026
|
+
getAll(): NodeDefinition[];
|
|
1027
|
+
/** Get node definitions by category */
|
|
1028
|
+
getByCategory(category: NodeCategory): NodeDefinition[];
|
|
1029
|
+
/** Get all registered nodeType strings */
|
|
1030
|
+
getRegisteredTypes(): string[];
|
|
1031
|
+
/** Check if registered */
|
|
1032
|
+
has(nodeType: string): boolean;
|
|
1033
|
+
/** Remove (for testing or hot reload) */
|
|
1034
|
+
unregister(nodeType: string): boolean;
|
|
1035
|
+
/** Clear all (for testing) */
|
|
1036
|
+
clear(): void;
|
|
1037
|
+
/** Get node default ports (backward-compatible with getDefaultPorts) */
|
|
1038
|
+
getDefaultPorts(nodeType: string): {
|
|
1039
|
+
inputs: InputPort[];
|
|
1040
|
+
outputs: OutputPort[];
|
|
1041
|
+
};
|
|
1042
|
+
/** Get node default parameters (backward-compatible with getDefaultParams) */
|
|
1043
|
+
getDefaultParams(nodeType: string): Record<string, unknown>;
|
|
1044
|
+
/** Get node default label (backward-compatible with getDefaultLabel) */
|
|
1045
|
+
getDefaultLabel(nodeType: string): string;
|
|
1046
|
+
/** Infer category from node type (backward-compatible with getCategoryForType) */
|
|
1047
|
+
getCategoryForType(nodeType: string): NodeCategory;
|
|
1048
|
+
/**
|
|
1049
|
+
* Group nodes by group (for NodeLibrary UI)
|
|
1050
|
+
* Return format is compatible with the original NodeLibrary.tsx nodeTemplates
|
|
1051
|
+
*/
|
|
1052
|
+
getGroupedDefinitions(): Record<string, {
|
|
1053
|
+
icon: string;
|
|
1054
|
+
color: string;
|
|
1055
|
+
templates: Array<{
|
|
1056
|
+
nodeType: string;
|
|
1057
|
+
label: string;
|
|
1058
|
+
icon: string;
|
|
1059
|
+
category: NodeCategory;
|
|
1060
|
+
}>;
|
|
1061
|
+
}>;
|
|
1062
|
+
}
|
|
1063
|
+
declare const nodeRegistry: NodeRegistry;
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Flow2Code Split Storage
|
|
1067
|
+
*
|
|
1068
|
+
* Solves keypoint.md #2 "JSON Diff Hell" problem.
|
|
1069
|
+
*
|
|
1070
|
+
* Splits a single .flow.json into a directory structure:
|
|
1071
|
+
*
|
|
1072
|
+
* my-flow/
|
|
1073
|
+
* ├── meta.yaml ← Workflow metadata
|
|
1074
|
+
* ├── edges.yaml ← All edges
|
|
1075
|
+
* └── nodes/
|
|
1076
|
+
* ├── trigger_1.yaml
|
|
1077
|
+
* ├── fetch_1.yaml
|
|
1078
|
+
* └── response_1.yaml
|
|
1079
|
+
*
|
|
1080
|
+
* Each node is an independent file, greatly reducing Git merge conflict probability.
|
|
1081
|
+
*/
|
|
1082
|
+
|
|
1083
|
+
/** Split file structure */
|
|
1084
|
+
interface SplitFiles {
|
|
1085
|
+
/** meta.yaml content */
|
|
1086
|
+
meta: string;
|
|
1087
|
+
/** edges.yaml content */
|
|
1088
|
+
edges: string;
|
|
1089
|
+
/** Node files: filename → yaml content */
|
|
1090
|
+
nodes: Map<string, string>;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Split FlowIR into multiple YAML files
|
|
1094
|
+
*/
|
|
1095
|
+
declare function splitIR(ir: FlowIR): SplitFiles;
|
|
1096
|
+
/**
|
|
1097
|
+
* Merge YAML files into FlowIR
|
|
1098
|
+
*/
|
|
1099
|
+
declare function mergeIR(files: SplitFiles): FlowIR;
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Flow2Code FlowProject — Unified Flow load / save interface
|
|
1103
|
+
*
|
|
1104
|
+
* Solves the "Git version control workaround" problem:
|
|
1105
|
+
* Developers don't need to manually run split / merge;
|
|
1106
|
+
* all load / save operations auto-detect format and default to split YAML directory storage.
|
|
1107
|
+
*
|
|
1108
|
+
* Supports two formats:
|
|
1109
|
+
* 1. **Split YAML (default)** — `my-flow/` directory (`meta.yaml` + `edges.yaml` + `nodes/*.yaml`)
|
|
1110
|
+
* 2. **Single JSON (backward compatible)** — `my-flow.flow.json`
|
|
1111
|
+
*
|
|
1112
|
+
* @example
|
|
1113
|
+
* ```ts
|
|
1114
|
+
* import { loadFlowProject, saveFlowProject } from "flow2code/compiler";
|
|
1115
|
+
*
|
|
1116
|
+
* // Load (auto-detect format)
|
|
1117
|
+
* const ir = loadFlowProject("./flows/my-flow");
|
|
1118
|
+
*
|
|
1119
|
+
* // Save (defaults to split YAML directory)
|
|
1120
|
+
* saveFlowProject(ir, "./flows/my-flow");
|
|
1121
|
+
*
|
|
1122
|
+
* // Force save as .flow.json
|
|
1123
|
+
* saveFlowProject(ir, "./flows/my-flow.flow.json", { format: "json" });
|
|
1124
|
+
* ```
|
|
1125
|
+
*/
|
|
1126
|
+
|
|
1127
|
+
type FlowProjectFormat = "split" | "json";
|
|
1128
|
+
interface SaveOptions {
|
|
1129
|
+
/** Storage format, default "split" (YAML directory) */
|
|
1130
|
+
format?: FlowProjectFormat;
|
|
1131
|
+
/** Whether to clean orphaned node files in directory (default true) */
|
|
1132
|
+
cleanOrphanNodes?: boolean;
|
|
1133
|
+
}
|
|
1134
|
+
interface FlowProjectInfo {
|
|
1135
|
+
/** Actual loaded path */
|
|
1136
|
+
path: string;
|
|
1137
|
+
/** Detected format */
|
|
1138
|
+
format: FlowProjectFormat;
|
|
1139
|
+
/** Loaded IR */
|
|
1140
|
+
ir: FlowIR;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Detect flow project format from path
|
|
1144
|
+
*
|
|
1145
|
+
* Detection order:
|
|
1146
|
+
* 1. Path ends with `.flow.json` → json
|
|
1147
|
+
* 2. Path is a directory containing `meta.yaml` → split
|
|
1148
|
+
* 3. Path + `.flow.json` file exists → json
|
|
1149
|
+
* 4. Path is a directory (assumed split) → split
|
|
1150
|
+
*/
|
|
1151
|
+
declare function detectFormat(inputPath: string): {
|
|
1152
|
+
resolvedPath: string;
|
|
1153
|
+
format: FlowProjectFormat;
|
|
1154
|
+
};
|
|
1155
|
+
/**
|
|
1156
|
+
* Load Flow project (auto-detect format)
|
|
1157
|
+
*/
|
|
1158
|
+
declare function loadFlowProject(inputPath: string): FlowProjectInfo;
|
|
1159
|
+
/**
|
|
1160
|
+
* Save Flow project
|
|
1161
|
+
*
|
|
1162
|
+
* @param format - Default "split". Pass "json" to save as single .flow.json.
|
|
1163
|
+
*/
|
|
1164
|
+
declare function saveFlowProject(ir: FlowIR, outputPath: string, options?: SaveOptions): string[];
|
|
1165
|
+
/**
|
|
1166
|
+
* Migrate .flow.json to split YAML directory
|
|
1167
|
+
* Returns list of written files. The original .flow.json is not deleted.
|
|
1168
|
+
*/
|
|
1169
|
+
declare function migrateToSplit(jsonPath: string): string[];
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Flow2Code Semantic Diff Engine
|
|
1173
|
+
*
|
|
1174
|
+
* Compares two FlowIR versions, producing human-readable semantic diff descriptions.
|
|
1175
|
+
* Replaces the noise of raw JSON diffs, letting PR reviews see "what logic was added" instead of "which bytes changed".
|
|
1176
|
+
*
|
|
1177
|
+
* Comparison levels:
|
|
1178
|
+
* 1. Meta: name, description changes
|
|
1179
|
+
* 2. Nodes: added / removed / modified nodes
|
|
1180
|
+
* 3. Edges: added / removed edges
|
|
1181
|
+
* 4. Params: node parameter detail changes
|
|
1182
|
+
*/
|
|
1183
|
+
|
|
1184
|
+
type ChangeType = "added" | "removed" | "modified";
|
|
1185
|
+
type ChangeCategory = "meta" | "node" | "edge";
|
|
1186
|
+
interface SemanticChange {
|
|
1187
|
+
/** Change type */
|
|
1188
|
+
type: ChangeType;
|
|
1189
|
+
/** Change category */
|
|
1190
|
+
category: ChangeCategory;
|
|
1191
|
+
/** Affected ID (nodeId or edgeId) */
|
|
1192
|
+
id: string;
|
|
1193
|
+
/** Human-readable description */
|
|
1194
|
+
description: string;
|
|
1195
|
+
/** Detailed field differences for modifications */
|
|
1196
|
+
details?: FieldDiff[];
|
|
1197
|
+
}
|
|
1198
|
+
interface FieldDiff {
|
|
1199
|
+
/** Field path, e.g. "params.url" or "label" */
|
|
1200
|
+
field: string;
|
|
1201
|
+
/** Old value */
|
|
1202
|
+
before: unknown;
|
|
1203
|
+
/** New value */
|
|
1204
|
+
after: unknown;
|
|
1205
|
+
}
|
|
1206
|
+
interface DiffSummary {
|
|
1207
|
+
/** All changes */
|
|
1208
|
+
changes: SemanticChange[];
|
|
1209
|
+
/** Quick statistics */
|
|
1210
|
+
stats: {
|
|
1211
|
+
added: number;
|
|
1212
|
+
removed: number;
|
|
1213
|
+
modified: number;
|
|
1214
|
+
total: number;
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Compute semantic differences between two FlowIRs
|
|
1219
|
+
*
|
|
1220
|
+
* @param before - IR before changes
|
|
1221
|
+
* @param after - IR after changes
|
|
1222
|
+
* @returns Semantic diff summary
|
|
1223
|
+
*/
|
|
1224
|
+
declare function semanticDiff(before: FlowIR, after: FlowIR): DiffSummary;
|
|
1225
|
+
/**
|
|
1226
|
+
* Format diff summary into a human-readable text report
|
|
1227
|
+
*/
|
|
1228
|
+
declare function formatDiff(summary: DiffSummary): string;
|
|
1229
|
+
|
|
1230
|
+
export { ActionType, type AuditHint, type BuiltinPlatformName, CURRENT_IR_VERSION, type CallSubflowParams, type CompileOptions, type CompileResult, type CronJobParams, type CustomCodeParams, type DeclareVariableParams, type DecompileOptions, type DecompileResult, type DependencyReport, type ExecutionPlan, type ExecutionStep, type FetchApiParams, type FlowDataType, type FlowEdge, type FlowIR, type FlowNode, type FlowProjectFormat, type FlowProjectInfo, type ForLoopParams, type HttpWebhookParams, type IfElseParams, type InputPort, LogicType, type ManualTriggerParams, NodeCategory, type NodeDefinition, type NodeId, type NodePlugin, NodeRegistry, type NodeType, type OutputPort, OutputType, type PlatformAdapter, type PlatformContext, type PlatformName, type PluginContext, type PluginRegistry, type PortId, type RedisCacheParams, type ReturnResponseParams, type SaveOptions, type SecurityCheckResult, type SecurityFinding, type SourceMap, type SplitFiles, type SqlQueryParams, type TraceResult, type TracerOptions, type TransformParams, type TriggerInitContext, TriggerType, type TryCatchParams, VariableType, builtinPlugins, clearPlugins, compile, createPluginRegistry, decompile, detectFormat, formatDiff, formatSecurityReport, formatTraceResults, getAllPlugins, getAvailablePlatforms, getPlatform, getPlugin, hasPlugin, inferFlowStateTypes, installFlowTracer, loadFlowProject, mergeIR, migrateToSplit, nodeRegistry, parseExpression, registerPlatform, registerPlugin, registerPlugins, saveFlowProject, semanticDiff, splitIR, topologicalSort, traceError, traceLineToNode, validateFlowIR as validate, validateFlowIR, validateIRSecurity, withFlowTrace };
|