@n8n-as-code/transformer 0.2.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.
Files changed (70) hide show
  1. package/README.md +215 -0
  2. package/dist/compiler/index.d.ts +6 -0
  3. package/dist/compiler/index.d.ts.map +1 -0
  4. package/dist/compiler/index.js +6 -0
  5. package/dist/compiler/index.js.map +1 -0
  6. package/dist/compiler/typescript-parser.d.ts +87 -0
  7. package/dist/compiler/typescript-parser.d.ts.map +1 -0
  8. package/dist/compiler/typescript-parser.js +379 -0
  9. package/dist/compiler/typescript-parser.js.map +1 -0
  10. package/dist/compiler/workflow-builder.d.ts +48 -0
  11. package/dist/compiler/workflow-builder.d.ts.map +1 -0
  12. package/dist/compiler/workflow-builder.js +231 -0
  13. package/dist/compiler/workflow-builder.js.map +1 -0
  14. package/dist/decorators/helpers.d.ts +27 -0
  15. package/dist/decorators/helpers.d.ts.map +1 -0
  16. package/dist/decorators/helpers.js +97 -0
  17. package/dist/decorators/helpers.js.map +1 -0
  18. package/dist/decorators/index.d.ts +11 -0
  19. package/dist/decorators/index.d.ts.map +1 -0
  20. package/dist/decorators/index.js +10 -0
  21. package/dist/decorators/index.js.map +1 -0
  22. package/dist/decorators/links.d.ts +70 -0
  23. package/dist/decorators/links.d.ts.map +1 -0
  24. package/dist/decorators/links.js +85 -0
  25. package/dist/decorators/links.js.map +1 -0
  26. package/dist/decorators/node.d.ts +32 -0
  27. package/dist/decorators/node.d.ts.map +1 -0
  28. package/dist/decorators/node.js +43 -0
  29. package/dist/decorators/node.js.map +1 -0
  30. package/dist/decorators/types.d.ts +122 -0
  31. package/dist/decorators/types.d.ts.map +1 -0
  32. package/dist/decorators/types.js +13 -0
  33. package/dist/decorators/types.js.map +1 -0
  34. package/dist/decorators/workflow.d.ts +32 -0
  35. package/dist/decorators/workflow.d.ts.map +1 -0
  36. package/dist/decorators/workflow.js +38 -0
  37. package/dist/decorators/workflow.js.map +1 -0
  38. package/dist/index.d.ts +14 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +15 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/parser/ast-to-typescript.d.ts +55 -0
  43. package/dist/parser/ast-to-typescript.d.ts.map +1 -0
  44. package/dist/parser/ast-to-typescript.js +337 -0
  45. package/dist/parser/ast-to-typescript.js.map +1 -0
  46. package/dist/parser/index.d.ts +6 -0
  47. package/dist/parser/index.d.ts.map +1 -0
  48. package/dist/parser/index.js +6 -0
  49. package/dist/parser/index.js.map +1 -0
  50. package/dist/parser/json-to-ast.d.ts +61 -0
  51. package/dist/parser/json-to-ast.d.ts.map +1 -0
  52. package/dist/parser/json-to-ast.js +222 -0
  53. package/dist/parser/json-to-ast.js.map +1 -0
  54. package/dist/types.d.ts +227 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +7 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/utils/formatting.d.ts +33 -0
  59. package/dist/utils/formatting.d.ts.map +1 -0
  60. package/dist/utils/formatting.js +83 -0
  61. package/dist/utils/formatting.js.map +1 -0
  62. package/dist/utils/index.d.ts +6 -0
  63. package/dist/utils/index.d.ts.map +1 -0
  64. package/dist/utils/index.js +6 -0
  65. package/dist/utils/index.js.map +1 -0
  66. package/dist/utils/naming.d.ts +39 -0
  67. package/dist/utils/naming.d.ts.map +1 -0
  68. package/dist/utils/naming.js +161 -0
  69. package/dist/utils/naming.js.map +1 -0
  70. package/package.json +33 -0
package/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # @n8n-as-code/transformer
2
+
3
+ > **✨ NEW in v0.2.0**: This package enables the new **TypeScript workflow format** (`.workflow.ts`) that replaces JSON as the default storage format across n8n-as-code.
4
+
5
+ Bidirectional transformer for n8n workflows: JSON ↔ TypeScript
6
+
7
+ ## Overview
8
+
9
+ This package provides the core transformation engine that converts:
10
+ - **JSON → TypeScript**: n8n workflow JSON (from API) → TypeScript class with decorators
11
+ - **TypeScript → JSON**: TypeScript workflow class → n8n workflow JSON (for API)
12
+
13
+ ## Features
14
+
15
+ - ✅ **Bidirectional transformation** with roundtrip support
16
+ - ✅ **TypeScript decorators** for clean, readable workflow definitions
17
+ - ✅ **Auto-layout support** for AI-generated workflows (optional positions)
18
+ - ✅ **Name collision handling** (HttpRequest1, HttpRequest2, ...)
19
+ - ✅ **Prettier integration** for formatted output
20
+ - ✅ **AI dependency injection** syntax for LangChain nodes
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @n8n-as-code/transformer
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### For Workflow Authors (TypeScript)
31
+
32
+ ```typescript
33
+ import { workflow, node, links } from '@n8n-as-code/transformer';
34
+
35
+ @workflow({
36
+ id: "unique-workflow-id",
37
+ name: "My Workflow",
38
+ active: true,
39
+ settings: { executionOrder: "v1" }
40
+ })
41
+ export class MyWorkflow {
42
+
43
+ @node({
44
+ name: "Schedule Trigger",
45
+ type: "n8n-nodes-base.scheduleTrigger",
46
+ version: 1.2,
47
+ position: [0, 0]
48
+ })
49
+ ScheduleTrigger = {
50
+ rule: {
51
+ interval: [{
52
+ field: "cronExpression",
53
+ expression: "0 9 * * 1-5"
54
+ }]
55
+ }
56
+ };
57
+
58
+ @node({
59
+ name: "HTTP Request",
60
+ type: "n8n-nodes-base.httpRequest",
61
+ version: 4,
62
+ position: [200, 0]
63
+ })
64
+ HttpRequest = {
65
+ url: "https://api.example.com/data",
66
+ method: "GET"
67
+ };
68
+
69
+ @links()
70
+ defineRouting() {
71
+ this.ScheduleTrigger.out(0).to(this.HttpRequest.in(0));
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### For Package Developers (Transformation)
77
+
78
+ ```typescript
79
+ import { JsonToAstParser, AstToTypeScriptGenerator } from '@n8n-as-code/transformer';
80
+
81
+ // JSON → TypeScript
82
+ const parser = new JsonToAstParser();
83
+ const ast = parser.parse(workflowJson);
84
+
85
+ const generator = new AstToTypeScriptGenerator();
86
+ const tsCode = await generator.generate(ast, {
87
+ format: true,
88
+ commentStyle: 'verbose'
89
+ });
90
+
91
+ console.log(tsCode); // Ready to write to .workflow.ts file
92
+ ```
93
+
94
+ ```typescript
95
+ import { TypeScriptParser, WorkflowBuilder } from '@n8n-as-code/transformer';
96
+
97
+ // TypeScript → JSON (Phase 2 - coming soon)
98
+ const parser = new TypeScriptParser();
99
+ const ast = await parser.parseFile('my-workflow.ts');
100
+
101
+ const builder = new WorkflowBuilder();
102
+ const workflowJson = builder.build(ast);
103
+
104
+ console.log(workflowJson); // Ready to push to n8n API
105
+ ```
106
+
107
+ ## Architecture
108
+
109
+ ```
110
+ ┌─────────────────────────────────────────────┐
111
+ │ │
112
+ │ n8n JSON Workflow (API) │
113
+ │ │
114
+ └──────────────┬──────────────────────────────┘
115
+
116
+ │ JsonToAstParser
117
+
118
+ ┌─────────────────────────────────────────────┐
119
+ │ │
120
+ │ WorkflowAST (Intermediate) │
121
+ │ - Normalized structure │
122
+ │ - Property names instead of UUIDs │
123
+ │ │
124
+ └──────────────┬──────────────────────────────┘
125
+
126
+ │ AstToTypeScriptGenerator
127
+
128
+ ┌─────────────────────────────────────────────┐
129
+ │ │
130
+ │ TypeScript Workflow (.workflow.ts) │
131
+ │ - Decorators (@workflow, @node, @links) │
132
+ │ - Human-readable property names │
133
+ │ - Formatted with Prettier │
134
+ │ │
135
+ └─────────────────────────────────────────────┘
136
+ ```
137
+
138
+ ## Status
139
+
140
+ - ✅ **Phase 1**: Architecture & decorators (COMPLETE)
141
+ - 🚧 **Phase 2**: Core transformation logic (IN PROGRESS)
142
+ - ⏳ **Phase 3**: Integration with @n8n-as-code/sync
143
+ - ⏳ **Phase 4**: Integration with @n8n-as-code/skills
144
+
145
+ ## API Reference
146
+
147
+ ### Decorators
148
+
149
+ #### `@workflow(metadata)`
150
+
151
+ Marks a class as an n8n workflow.
152
+
153
+ **Parameters:**
154
+ - `id`: Workflow ID (UUID)
155
+ - `name`: Workflow name
156
+ - `active`: Whether workflow is active
157
+ - `settings?`: Workflow settings (executionOrder, etc.)
158
+
159
+ #### `@node(metadata)`
160
+
161
+ Marks a property as an n8n node.
162
+
163
+ **Parameters:**
164
+ - `name`: Node display name
165
+ - `type`: Node type (e.g., "n8n-nodes-base.httpRequest")
166
+ - `version`: Node version
167
+ - `position?`: [x, y] coordinates (optional for auto-layout)
168
+ - `credentials?`: Node credentials
169
+ - `onError?`: Error handling behavior
170
+
171
+ #### `@links()`
172
+
173
+ Marks the method that defines workflow routing.
174
+
175
+ ### Transformation Classes
176
+
177
+ #### `JsonToAstParser`
178
+
179
+ Parses n8n JSON to intermediate AST.
180
+
181
+ ```typescript
182
+ const parser = new JsonToAstParser();
183
+ const ast = parser.parse(workflowJson);
184
+ ```
185
+
186
+ #### `AstToTypeScriptGenerator`
187
+
188
+ Generates TypeScript code from AST.
189
+
190
+ ```typescript
191
+ const generator = new AstToTypeScriptGenerator();
192
+ const code = await generator.generate(ast, options);
193
+ ```
194
+
195
+ **Options:**
196
+ - `format?: boolean` - Apply Prettier formatting (default: true)
197
+ - `commentStyle?: 'minimal' | 'verbose'` - Comment style (default: 'verbose')
198
+ - `className?: string` - Custom class name
199
+
200
+ ## Development
201
+
202
+ ```bash
203
+ # Build
204
+ npm run build
205
+
206
+ # Tests
207
+ npm test
208
+
209
+ # Type check
210
+ npm run typecheck
211
+ ```
212
+
213
+ ## License
214
+
215
+ See LICENSE in repository root.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Compiler exports
3
+ */
4
+ export { TypeScriptParser } from './typescript-parser.js';
5
+ export { WorkflowBuilder } from './workflow-builder.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/compiler/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Compiler exports
3
+ */
4
+ export { TypeScriptParser } from './typescript-parser.js';
5
+ export { WorkflowBuilder } from './workflow-builder.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/compiler/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * TypeScript Parser
3
+ *
4
+ * Parses TypeScript workflow files using ts-morph
5
+ * Extracts metadata from decorators and class structure
6
+ */
7
+ import { WorkflowAST } from '../types.js';
8
+ /**
9
+ * Parse TypeScript workflow file
10
+ */
11
+ export declare class TypeScriptParser {
12
+ private project;
13
+ constructor();
14
+ /**
15
+ * Parse TypeScript file
16
+ */
17
+ parseFile(filePath: string): Promise<WorkflowAST>;
18
+ /**
19
+ * Parse TypeScript code string
20
+ */
21
+ parseCode(code: string): Promise<WorkflowAST>;
22
+ /**
23
+ * Parse source file to AST
24
+ */
25
+ private parseSourceFile;
26
+ /**
27
+ * Find class decorated with @workflow
28
+ */
29
+ private findWorkflowClass;
30
+ /**
31
+ * Extract workflow metadata from @workflow decorator
32
+ */
33
+ private extractWorkflowMetadata;
34
+ /**
35
+ * Extract nodes from class properties with @node decorator
36
+ */
37
+ private extractNodes;
38
+ /**
39
+ * Extract connections from @links method
40
+ */
41
+ private extractConnections;
42
+ /**
43
+ * Extract AI dependencies from .uses() calls in @links method
44
+ *
45
+ * Example:
46
+ * this.AgentIa.uses({
47
+ * ai_languageModel: this.OpenaiChatModel.output,
48
+ * ai_memory: this.Mmoire.output,
49
+ * ai_tool: [this.Tool1.output, this.Tool2.output]
50
+ * });
51
+ */
52
+ private extractAIDependencies;
53
+ /**
54
+ * Parse AI dependencies object from .uses() call
55
+ *
56
+ * Input: "ai_languageModel: this.Model.output, ai_memory: this.Memory.output"
57
+ * Output: { ai_languageModel: "Model", ai_memory: "Memory" }
58
+ */
59
+ private parseAIDependencies;
60
+ /**
61
+ * Split string by commas, but not inside brackets
62
+ */
63
+ private splitByTopLevelCommas;
64
+ /**
65
+ * Parse tool array
66
+ *
67
+ * Input: "[this.Tool1.output, this.Tool2.output]"
68
+ * Output: ["Tool1", "Tool2"]
69
+ */
70
+ private parseToolArray;
71
+ /**
72
+ * Parse a connection statement
73
+ *
74
+ * Examples:
75
+ * this.ScheduleTrigger.out(0).to(this.HttpRequest.in(0));
76
+ * this.GithubCheck.error().to(this.CreateBranch.in(0));
77
+ */
78
+ private parseConnectionStatement;
79
+ /**
80
+ * Parse object literal string to object
81
+ *
82
+ * Uses Function constructor for safe eval of object literals
83
+ * This is safe because we only parse our own generated code
84
+ */
85
+ private parseObjectLiteral;
86
+ }
87
+ //# sourceMappingURL=typescript-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typescript-parser.d.ts","sourceRoot":"","sources":["../../src/compiler/typescript-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,WAAW,EAA4C,MAAM,aAAa,CAAC;AAEpF;;GAEG;AACH,qBAAa,gBAAgB;IACzB,OAAO,CAAC,OAAO,CAAU;;IAazB;;OAEG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAKvD;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAKnD;;OAEG;IACH,OAAO,CAAC,eAAe;IA2BvB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgBzB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAwB/B;;OAEG;IACH,OAAO,CAAC,YAAY;IA2CpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAiD1B;;;;;;;;;OASG;IACH,OAAO,CAAC,qBAAqB;IA2D7B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAoC3B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAyB7B;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAmBtB;;;;;;OAMG;IACH,OAAO,CAAC,wBAAwB;IA2ChC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;CAU7B"}
@@ -0,0 +1,379 @@
1
+ /**
2
+ * TypeScript Parser
3
+ *
4
+ * Parses TypeScript workflow files using ts-morph
5
+ * Extracts metadata from decorators and class structure
6
+ */
7
+ import { Project, SyntaxKind } from 'ts-morph';
8
+ /**
9
+ * Parse TypeScript workflow file
10
+ */
11
+ export class TypeScriptParser {
12
+ project;
13
+ constructor() {
14
+ this.project = new Project({
15
+ compilerOptions: {
16
+ target: 99, // ESNext
17
+ module: 99, // ESNext
18
+ experimentalDecorators: true,
19
+ emitDecoratorMetadata: true
20
+ }
21
+ });
22
+ }
23
+ /**
24
+ * Parse TypeScript file
25
+ */
26
+ async parseFile(filePath) {
27
+ const sourceFile = this.project.addSourceFileAtPath(filePath);
28
+ return this.parseSourceFile(sourceFile);
29
+ }
30
+ /**
31
+ * Parse TypeScript code string
32
+ */
33
+ async parseCode(code) {
34
+ const sourceFile = this.project.createSourceFile('temp.ts', code, { overwrite: true });
35
+ return this.parseSourceFile(sourceFile);
36
+ }
37
+ /**
38
+ * Parse source file to AST
39
+ */
40
+ parseSourceFile(sourceFile) {
41
+ // Find class with @workflow decorator
42
+ const workflowClass = this.findWorkflowClass(sourceFile);
43
+ if (!workflowClass) {
44
+ throw new Error('No class with @workflow decorator found in file');
45
+ }
46
+ // Extract workflow metadata
47
+ const metadata = this.extractWorkflowMetadata(workflowClass);
48
+ // Extract nodes
49
+ const nodes = this.extractNodes(workflowClass);
50
+ // Extract connections
51
+ const connections = this.extractConnections(workflowClass);
52
+ // Extract AI dependencies and add them to nodes
53
+ this.extractAIDependencies(workflowClass, nodes);
54
+ return {
55
+ metadata,
56
+ nodes,
57
+ connections
58
+ };
59
+ }
60
+ /**
61
+ * Find class decorated with @workflow
62
+ */
63
+ findWorkflowClass(sourceFile) {
64
+ const classes = sourceFile.getClasses();
65
+ for (const cls of classes) {
66
+ const decorators = cls.getDecorators();
67
+ for (const decorator of decorators) {
68
+ const decoratorName = decorator.getName();
69
+ if (decoratorName === 'workflow') {
70
+ return cls;
71
+ }
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ /**
77
+ * Extract workflow metadata from @workflow decorator
78
+ */
79
+ extractWorkflowMetadata(workflowClass) {
80
+ const decorator = workflowClass.getDecorator('workflow');
81
+ if (!decorator) {
82
+ throw new Error('Class missing @workflow decorator');
83
+ }
84
+ // Get decorator arguments
85
+ const args = decorator.getArguments();
86
+ if (args.length === 0) {
87
+ throw new Error('@workflow decorator missing metadata argument');
88
+ }
89
+ // Parse object literal argument
90
+ const metadataArg = args[0];
91
+ const metadataText = metadataArg.getText();
92
+ // Use eval in a safe context to parse the object literal
93
+ // This is safe because we're only parsing our own generated code
94
+ const metadata = this.parseObjectLiteral(metadataText);
95
+ return metadata;
96
+ }
97
+ /**
98
+ * Extract nodes from class properties with @node decorator
99
+ */
100
+ extractNodes(workflowClass) {
101
+ const nodes = [];
102
+ const properties = workflowClass.getProperties();
103
+ for (const prop of properties) {
104
+ const decorator = prop.getDecorator('node');
105
+ if (!decorator) {
106
+ continue; // Skip properties without @node decorator
107
+ }
108
+ // Extract node metadata from decorator
109
+ const args = decorator.getArguments();
110
+ if (args.length === 0) {
111
+ continue;
112
+ }
113
+ const metadataText = args[0].getText();
114
+ const metadata = this.parseObjectLiteral(metadataText);
115
+ // Extract property name
116
+ const propertyName = prop.getName();
117
+ // Extract parameters from property initializer
118
+ const initializer = prop.getInitializer();
119
+ const parameters = initializer ? this.parseObjectLiteral(initializer.getText()) : {};
120
+ nodes.push({
121
+ propertyName,
122
+ displayName: metadata.name,
123
+ type: metadata.type,
124
+ version: metadata.version,
125
+ position: metadata.position || [0, 0],
126
+ credentials: metadata.credentials,
127
+ onError: metadata.onError,
128
+ parameters
129
+ // aiDependencies will be added by extractAIDependencies()
130
+ });
131
+ }
132
+ return nodes;
133
+ }
134
+ /**
135
+ * Extract connections from @links method
136
+ */
137
+ extractConnections(workflowClass) {
138
+ const connections = [];
139
+ // Find method with @links decorator
140
+ const methods = workflowClass.getMethods();
141
+ let linksMethod = null;
142
+ for (const method of methods) {
143
+ const decorator = method.getDecorator('links');
144
+ if (decorator) {
145
+ linksMethod = method;
146
+ break;
147
+ }
148
+ }
149
+ if (!linksMethod) {
150
+ return connections; // No connections defined
151
+ }
152
+ // Parse method body to extract connections
153
+ const body = linksMethod.getBody();
154
+ if (!body || !body.isKind(SyntaxKind.Block)) {
155
+ return connections;
156
+ }
157
+ // Get all statements in the method
158
+ const statements = body.getStatements();
159
+ for (const statement of statements) {
160
+ const text = statement.getText();
161
+ // Parse connection statements
162
+ // Format: this.NodeA.out(0).to(this.NodeB.in(0));
163
+ // Format: this.NodeA.error().to(this.NodeB.in(0));
164
+ // Skip .uses() calls (handled by extractAIDependencies)
165
+ if (text.includes('.uses(')) {
166
+ continue;
167
+ }
168
+ const connection = this.parseConnectionStatement(text);
169
+ if (connection) {
170
+ connections.push(connection);
171
+ }
172
+ }
173
+ return connections;
174
+ }
175
+ /**
176
+ * Extract AI dependencies from .uses() calls in @links method
177
+ *
178
+ * Example:
179
+ * this.AgentIa.uses({
180
+ * ai_languageModel: this.OpenaiChatModel.output,
181
+ * ai_memory: this.Mmoire.output,
182
+ * ai_tool: [this.Tool1.output, this.Tool2.output]
183
+ * });
184
+ */
185
+ extractAIDependencies(workflowClass, nodes) {
186
+ // Find method with @links decorator
187
+ const methods = workflowClass.getMethods();
188
+ let linksMethod = null;
189
+ for (const method of methods) {
190
+ const decorator = method.getDecorator('links');
191
+ if (decorator) {
192
+ linksMethod = method;
193
+ break;
194
+ }
195
+ }
196
+ if (!linksMethod) {
197
+ return; // No links method
198
+ }
199
+ // Parse method body
200
+ const body = linksMethod.getBody();
201
+ if (!body || !body.isKind(SyntaxKind.Block)) {
202
+ return;
203
+ }
204
+ const statements = body.getStatements();
205
+ for (const statement of statements) {
206
+ const text = statement.getText();
207
+ // Only process .uses() calls
208
+ if (!text.includes('.uses(')) {
209
+ continue;
210
+ }
211
+ // Parse: this.NodeName.uses({ ... });
212
+ const usesMatch = text.match(/this\.(\w+)\.uses\s*\(\s*\{([^}]+)\}\s*\)/);
213
+ if (!usesMatch) {
214
+ continue;
215
+ }
216
+ const targetNodeProperty = usesMatch[1];
217
+ const depsObjectText = usesMatch[2];
218
+ // Find the corresponding node
219
+ const node = nodes.find(n => n.propertyName === targetNodeProperty);
220
+ if (!node) {
221
+ console.warn(`Warning: .uses() called on unknown node: ${targetNodeProperty}`);
222
+ continue;
223
+ }
224
+ // Parse dependencies object
225
+ const aiDependencies = this.parseAIDependencies(depsObjectText);
226
+ // Add to node
227
+ if (Object.keys(aiDependencies).length > 0) {
228
+ node.aiDependencies = aiDependencies;
229
+ }
230
+ }
231
+ }
232
+ /**
233
+ * Parse AI dependencies object from .uses() call
234
+ *
235
+ * Input: "ai_languageModel: this.Model.output, ai_memory: this.Memory.output"
236
+ * Output: { ai_languageModel: "Model", ai_memory: "Memory" }
237
+ */
238
+ parseAIDependencies(depsText) {
239
+ const result = {};
240
+ // Split by comma (but not inside brackets)
241
+ const entries = this.splitByTopLevelCommas(depsText);
242
+ for (const entry of entries) {
243
+ const trimmed = entry.trim();
244
+ if (!trimmed)
245
+ continue;
246
+ // Parse: key: value
247
+ const colonIndex = trimmed.indexOf(':');
248
+ if (colonIndex === -1)
249
+ continue;
250
+ const key = trimmed.substring(0, colonIndex).trim();
251
+ const value = trimmed.substring(colonIndex + 1).trim();
252
+ // Check if it's an array type (ai_tool or ai_document)
253
+ if ((key === 'ai_tool' || key === 'ai_document') && value.startsWith('[')) {
254
+ // Parse array: [this.Tool1.output, this.Tool2.output]
255
+ const itemNames = this.parseToolArray(value);
256
+ if (itemNames.length > 0) {
257
+ result[key] = itemNames;
258
+ }
259
+ }
260
+ else {
261
+ // Parse single reference: this.NodeName.output
262
+ const nodeMatch = value.match(/this\.(\w+)\.output/);
263
+ if (nodeMatch) {
264
+ result[key] = nodeMatch[1];
265
+ }
266
+ }
267
+ }
268
+ return result;
269
+ }
270
+ /**
271
+ * Split string by commas, but not inside brackets
272
+ */
273
+ splitByTopLevelCommas(text) {
274
+ const result = [];
275
+ let current = '';
276
+ let bracketDepth = 0;
277
+ for (const char of text) {
278
+ if (char === '[') {
279
+ bracketDepth++;
280
+ }
281
+ else if (char === ']') {
282
+ bracketDepth--;
283
+ }
284
+ else if (char === ',' && bracketDepth === 0) {
285
+ result.push(current);
286
+ current = '';
287
+ continue;
288
+ }
289
+ current += char;
290
+ }
291
+ if (current.trim()) {
292
+ result.push(current);
293
+ }
294
+ return result;
295
+ }
296
+ /**
297
+ * Parse tool array
298
+ *
299
+ * Input: "[this.Tool1.output, this.Tool2.output]"
300
+ * Output: ["Tool1", "Tool2"]
301
+ */
302
+ parseToolArray(arrayText) {
303
+ const result = [];
304
+ // Remove brackets
305
+ const content = arrayText.replace(/^\[|\]$/g, '').trim();
306
+ // Split by comma
307
+ const items = content.split(',');
308
+ for (const item of items) {
309
+ const nodeMatch = item.trim().match(/this\.(\w+)\.output/);
310
+ if (nodeMatch) {
311
+ result.push(nodeMatch[1]);
312
+ }
313
+ }
314
+ return result;
315
+ }
316
+ /**
317
+ * Parse a connection statement
318
+ *
319
+ * Examples:
320
+ * this.ScheduleTrigger.out(0).to(this.HttpRequest.in(0));
321
+ * this.GithubCheck.error().to(this.CreateBranch.in(0));
322
+ */
323
+ parseConnectionStatement(statement) {
324
+ // Remove whitespace and semicolon
325
+ const cleaned = statement.trim().replace(/;$/, '');
326
+ // Pattern: this.{fromNode}.{output}.to(this.{toNode}.in({input}))
327
+ const errorPattern = /this\.(\w+)\.error\(\)\.to\(this\.(\w+)\.in\((\d+)\)\)/;
328
+ const normalPattern = /this\.(\w+)\.out\((\d+)\)\.to\(this\.(\w+)\.in\((\d+)\)\)/;
329
+ // Try error pattern first
330
+ let match = cleaned.match(errorPattern);
331
+ if (match) {
332
+ return {
333
+ from: {
334
+ node: match[1],
335
+ output: 0,
336
+ isError: true
337
+ },
338
+ to: {
339
+ node: match[2],
340
+ input: parseInt(match[3])
341
+ }
342
+ };
343
+ }
344
+ // Try normal pattern
345
+ match = cleaned.match(normalPattern);
346
+ if (match) {
347
+ return {
348
+ from: {
349
+ node: match[1],
350
+ output: parseInt(match[2]),
351
+ isError: false
352
+ },
353
+ to: {
354
+ node: match[3],
355
+ input: parseInt(match[4])
356
+ }
357
+ };
358
+ }
359
+ return null;
360
+ }
361
+ /**
362
+ * Parse object literal string to object
363
+ *
364
+ * Uses Function constructor for safe eval of object literals
365
+ * This is safe because we only parse our own generated code
366
+ */
367
+ parseObjectLiteral(text) {
368
+ try {
369
+ // Wrap in parentheses and use Function constructor
370
+ const func = new Function(`return (${text})`);
371
+ return func();
372
+ }
373
+ catch (error) {
374
+ console.error('Failed to parse object literal:', text);
375
+ throw new Error(`Failed to parse object literal: ${error}`);
376
+ }
377
+ }
378
+ }
379
+ //# sourceMappingURL=typescript-parser.js.map