@adminide-stack/form-builder-core 5.1.4-alpha.43
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 +8 -0
- package/LICENSE +21 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/inngest/generateFunctionCode.d.ts +7 -0
- package/lib/inngest/generateFunctionCode.js +108 -0
- package/lib/inngest/stepGenerator.d.ts +25 -0
- package/lib/inngest/stepGenerator.js +266 -0
- package/lib/inngest/types.d.ts +47 -0
- package/lib/inngest/types.js +1 -0
- package/package.json +19 -0
- package/src/index.ts +3 -0
- package/src/inngest/generateFunctionCode.ts +150 -0
- package/src/inngest/stepGenerator.ts +316 -0
- package/src/inngest/types.ts +63 -0
- package/tsconfig.json +13 -0
- package/tsconfig.tsbuildinfo +1 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## [5.1.4-alpha.43](https://github.com/CDEBase/forms-stack/compare/v5.1.4-alpha.42...v5.1.4-alpha.43) (2025-09-23)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @adminide-stack/form-builder-core
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 CDMBase LLC.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { InngestFunctionDef, ExtractedFunction, StepFunction, GeneratedFunctionResult } from './types';
|
|
2
|
+
export { cleanStepCode, extractStepVarName } from './stepGenerator';
|
|
3
|
+
export declare function generateFunctionCode(def: InngestFunctionDef): string;
|
|
4
|
+
export declare function generateHandlerBody(def: InngestFunctionDef): string;
|
|
5
|
+
export declare function generateStepFunctionsFromDB(functionId: string, events: string[], extractedFunctions: ExtractedFunction[]): GeneratedFunctionResult;
|
|
6
|
+
export declare function wrapStepsInInngestFunction(functionDef: InngestFunctionDef, stepFunctions: StepFunction[]): string;
|
|
7
|
+
export declare function generateStepFromFunction(stepFunction: StepFunction, index: number): string;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { cleanStepCode, extractStepVarName, generateFromExtractedFunctions, extractFunctionBody, } from './stepGenerator';
|
|
2
|
+
// Re-export utilities for backward compatibility
|
|
3
|
+
export { cleanStepCode, extractStepVarName } from './stepGenerator';
|
|
4
|
+
// Check if code contains direct step operations (sleep, sendEvent, etc)
|
|
5
|
+
function hasDirectStepOperations(code) {
|
|
6
|
+
return /step\.(sleep|sendEvent|waitForEvent|run)\s*\(/.test(code);
|
|
7
|
+
}
|
|
8
|
+
function getDefaultDirectBody(type, id, label) {
|
|
9
|
+
switch (type) {
|
|
10
|
+
case 'sleep':
|
|
11
|
+
return `const ${id}_result = await step.sleep('${id}', '5s');`;
|
|
12
|
+
case 'sendEvent':
|
|
13
|
+
return `const ${id}_result = await step.sendEvent('${id}', { name: 'my.custom.event', data: { source: '${label || id}', ts: new Date().toISOString() } });`;
|
|
14
|
+
case 'waitForEvent':
|
|
15
|
+
return `const ${id}_result = await step.waitForEvent('${id}', { event: 'my.waited.event', timeout: '60s' });`;
|
|
16
|
+
default:
|
|
17
|
+
return `// not supported`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function indent(s, spaces) {
|
|
21
|
+
const pad = ' '.repeat(spaces);
|
|
22
|
+
return s
|
|
23
|
+
.split('\n')
|
|
24
|
+
.map((l) => (l ? pad + l : l))
|
|
25
|
+
.join('\n');
|
|
26
|
+
}
|
|
27
|
+
// Simple code formatter
|
|
28
|
+
function formatCode(code) {
|
|
29
|
+
// Basic formatting - in production, you'd use a proper formatter
|
|
30
|
+
return code
|
|
31
|
+
.split('\n')
|
|
32
|
+
.map((line) => line.trimEnd())
|
|
33
|
+
.join('\n')
|
|
34
|
+
.replace(/\n{3,}/g, '\n\n'); // Remove excessive blank lines
|
|
35
|
+
}
|
|
36
|
+
function generateStepBlock(step, position) {
|
|
37
|
+
var _a;
|
|
38
|
+
const stepVar = ((_a = (step.code || '').match(/const\s+(\w+)\s*=/)) === null || _a === void 0 ? void 0 : _a[1]) || `step_${position}`;
|
|
39
|
+
// If caller supplied code, embed it inside step.run wrapper by default for consistency
|
|
40
|
+
if (step.type === 'sleep' || step.type === 'sendEvent' || step.type === 'waitForEvent') {
|
|
41
|
+
// Execute directly (these typically call step.* helpers)
|
|
42
|
+
const body = extractFunctionBody(step.code || '') || getDefaultDirectBody(step.type, stepVar, step.name);
|
|
43
|
+
return `// ${step.description || step.name || `Step ${position}`}
|
|
44
|
+
${body}`;
|
|
45
|
+
}
|
|
46
|
+
const inner = extractFunctionBody(step.code || '') || 'return { success: true };';
|
|
47
|
+
return `// ${step.description || step.name || `Step ${position}`}
|
|
48
|
+
const ${stepVar}_result = await step.run('${stepVar}', async (event, step) => {
|
|
49
|
+
${indent(inner, 2)}
|
|
50
|
+
});`;
|
|
51
|
+
}
|
|
52
|
+
// Minimal shared generator. Replace internals by moving editor logic here incrementally.
|
|
53
|
+
export function generateFunctionCode(def) {
|
|
54
|
+
const eventsString = def.events.length > 0 ? def.events.map((e) => `'${e}'`).join(', ') : `'app.event'`;
|
|
55
|
+
const safeId = def.id.replace(/[^a-zA-Z0-9]/g, '') || 'function';
|
|
56
|
+
const body = generateHandlerBody(def);
|
|
57
|
+
return `const ${safeId}Function = inngest.createFunction(
|
|
58
|
+
{ id: '${def.id}' },
|
|
59
|
+
{ event: [${eventsString}] },
|
|
60
|
+
async ({ event, step }) => {
|
|
61
|
+
${indent(body, 4)}
|
|
62
|
+
}
|
|
63
|
+
);`;
|
|
64
|
+
}
|
|
65
|
+
// New: return only the handler body that backend executes and editor displays
|
|
66
|
+
export function generateHandlerBody(def) {
|
|
67
|
+
const stepBlocks = def.steps.map((step, index) => generateStepBlock(step, index + 1)).join('\n');
|
|
68
|
+
const footer = `return {\n functionId: '${def.id}',\n steps: ${def.steps.length},\n timestamp: new Date().toISOString()\n};`;
|
|
69
|
+
return `${stepBlocks}\n${footer}`;
|
|
70
|
+
}
|
|
71
|
+
// Generate Inngest function from database-extracted functions
|
|
72
|
+
export function generateStepFunctionsFromDB(functionId, events, extractedFunctions) {
|
|
73
|
+
if (!extractedFunctions || extractedFunctions.length === 0) {
|
|
74
|
+
throw new Error('No functions provided to generate');
|
|
75
|
+
}
|
|
76
|
+
// Use the new unified generator
|
|
77
|
+
const code = generateFromExtractedFunctions(extractedFunctions);
|
|
78
|
+
return {
|
|
79
|
+
code: formatCode(code),
|
|
80
|
+
functionId,
|
|
81
|
+
stepCount: extractedFunctions.length,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// Wrap multiple step functions in a single Inngest function
|
|
85
|
+
export function wrapStepsInInngestFunction(functionDef, stepFunctions) {
|
|
86
|
+
const extractedFunctions = stepFunctions.map((sf) => ({
|
|
87
|
+
id: sf.id,
|
|
88
|
+
name: sf.name,
|
|
89
|
+
code: sf.code,
|
|
90
|
+
description: sf.description,
|
|
91
|
+
}));
|
|
92
|
+
const result = generateStepFunctionsFromDB(functionDef.id, functionDef.events, extractedFunctions);
|
|
93
|
+
return result.code;
|
|
94
|
+
}
|
|
95
|
+
// Generate a single step from an extracted function
|
|
96
|
+
export function generateStepFromFunction(stepFunction, index) {
|
|
97
|
+
const stepVar = extractStepVarName(stepFunction.code) || `step_${index}`;
|
|
98
|
+
const functionBody = extractFunctionBody(stepFunction.code);
|
|
99
|
+
if (!functionBody) {
|
|
100
|
+
return `// ${stepFunction.name || `Step ${index}`}\nconst ${stepVar}_result = { success: false, error: 'Invalid function format' };`;
|
|
101
|
+
}
|
|
102
|
+
const hasDirectOps = hasDirectStepOperations(stepFunction.code);
|
|
103
|
+
const cleanedBody = cleanStepCode(functionBody);
|
|
104
|
+
if (hasDirectOps) {
|
|
105
|
+
return `// ${stepFunction.name || `Step ${index}`}\n${cleanedBody}`;
|
|
106
|
+
}
|
|
107
|
+
return `// ${stepFunction.name || `Step ${index}`}\nconst ${stepVar}_result = await step.run('${stepVar}', async (event, step) => {\n${indent(cleanedBody, 2)}\n});`;
|
|
108
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { InngestStep, ExtractedFunction } from './types';
|
|
2
|
+
export type { ExtractedFunction } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Step function generation utilities for unified code generation
|
|
5
|
+
* Used by both browser editor and server execution
|
|
6
|
+
*/
|
|
7
|
+
export declare function cleanStepCode(code: string): string;
|
|
8
|
+
export declare function extractStepVarName(code: string): string | null;
|
|
9
|
+
export declare function generateDefaultStepFunction(stepVar: string, stepType: string): string;
|
|
10
|
+
export declare function generateStepFunction(step: InngestStep | ExtractedFunction, index: number): {
|
|
11
|
+
code: string;
|
|
12
|
+
varName: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function extractFunctionBody(code: string): string | null;
|
|
15
|
+
export declare function transformStepForExecution(stepFunction: {
|
|
16
|
+
code: string;
|
|
17
|
+
varName: string;
|
|
18
|
+
}, stepType: string, stepLabel: string): string;
|
|
19
|
+
export declare function generateInngestFunctionFromSteps(stepFunctions: Array<{
|
|
20
|
+
code: string;
|
|
21
|
+
varName: string;
|
|
22
|
+
type?: string;
|
|
23
|
+
label?: string;
|
|
24
|
+
}>): string;
|
|
25
|
+
export declare function generateFromExtractedFunctions(extractedFunctions: ExtractedFunction[]): string;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step function generation utilities for unified code generation
|
|
3
|
+
* Used by both browser editor and server execution
|
|
4
|
+
*/
|
|
5
|
+
// Pass through code without any cleaning/validation
|
|
6
|
+
export function cleanStepCode(code) {
|
|
7
|
+
return code;
|
|
8
|
+
}
|
|
9
|
+
// Extract step variable name from code
|
|
10
|
+
export function extractStepVarName(code) {
|
|
11
|
+
const match = code.match(/const\s+(\w+)\s*=/);
|
|
12
|
+
return match ? match[1] : null;
|
|
13
|
+
}
|
|
14
|
+
// Generate default step function for different step types
|
|
15
|
+
export function generateDefaultStepFunction(stepVar, stepType) {
|
|
16
|
+
switch (stepType) {
|
|
17
|
+
case 'sleep':
|
|
18
|
+
return `const ${stepVar} = async (event, step) => {
|
|
19
|
+
// Sleep for specified duration
|
|
20
|
+
const sleepResult = await step.sleep('${stepVar}', '5s');
|
|
21
|
+
|
|
22
|
+
console.log('Sleep completed:', sleepResult);
|
|
23
|
+
|
|
24
|
+
return { success: true, duration: '5s', result: sleepResult };
|
|
25
|
+
};`;
|
|
26
|
+
case 'sendEvent':
|
|
27
|
+
return `const ${stepVar} = async (event, step) => {
|
|
28
|
+
// Send an event to trigger other functions
|
|
29
|
+
const eventResult = await step.sendEvent('${stepVar}', {
|
|
30
|
+
name: 'my.custom.event',
|
|
31
|
+
data: {
|
|
32
|
+
source: '${stepVar}',
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
payload: event.data
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
console.log('Event sent:', eventResult);
|
|
39
|
+
|
|
40
|
+
return { success: true, eventSent: true, result: eventResult };
|
|
41
|
+
};`;
|
|
42
|
+
case 'waitForEvent':
|
|
43
|
+
return `const ${stepVar} = async (event, step) => {
|
|
44
|
+
// Wait for a specific event before continuing
|
|
45
|
+
const waitResult = await step.waitForEvent('${stepVar}', {
|
|
46
|
+
event: 'my.waited.event',
|
|
47
|
+
timeout: '60s'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log('Event received:', waitResult);
|
|
51
|
+
|
|
52
|
+
return { success: true, eventReceived: true, result: waitResult };
|
|
53
|
+
};`;
|
|
54
|
+
case 'retrieveData':
|
|
55
|
+
return `const ${stepVar} = async (event, step) => {
|
|
56
|
+
// Data retrieval implementation
|
|
57
|
+
const formData = event.data.formData || {};
|
|
58
|
+
|
|
59
|
+
// Process and retrieve data
|
|
60
|
+
const retrievedData = {
|
|
61
|
+
...formData,
|
|
62
|
+
processedAt: new Date().toISOString()
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return { success: true, data: retrievedData };
|
|
66
|
+
};`;
|
|
67
|
+
case 'validateForm':
|
|
68
|
+
return `const ${stepVar} = async (event, step) => {
|
|
69
|
+
// Validate form data
|
|
70
|
+
const formData = event.data.formData || {};
|
|
71
|
+
const errors = [];
|
|
72
|
+
|
|
73
|
+
// Add validation logic here
|
|
74
|
+
if (!formData.email) {
|
|
75
|
+
errors.push('Email is required');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
success: errors.length === 0,
|
|
80
|
+
errors,
|
|
81
|
+
validated: errors.length === 0
|
|
82
|
+
};
|
|
83
|
+
};`;
|
|
84
|
+
case 'parallel':
|
|
85
|
+
return `const ${stepVar} = async (event, step) => {
|
|
86
|
+
// Execute parallel operations
|
|
87
|
+
const results = await Promise.all([
|
|
88
|
+
// Add parallel operations here
|
|
89
|
+
Promise.resolve({ task: 'task1', result: 'completed' }),
|
|
90
|
+
Promise.resolve({ task: 'task2', result: 'completed' })
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
return { success: true, results };
|
|
94
|
+
};`;
|
|
95
|
+
default:
|
|
96
|
+
return `const ${stepVar} = async (event, step) => {
|
|
97
|
+
// Default step implementation
|
|
98
|
+
return { success: true };
|
|
99
|
+
};`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Generate a step function in editor format (const step_xxx = async (event, step) => {...})
|
|
103
|
+
export function generateStepFunction(step, index) {
|
|
104
|
+
// Extract or generate step variable name
|
|
105
|
+
const varName = extractStepVarName(step.code || '') ||
|
|
106
|
+
step.originalStepName ||
|
|
107
|
+
step.id ||
|
|
108
|
+
`step_${index}`;
|
|
109
|
+
// If step has code, use it; otherwise generate default
|
|
110
|
+
const code = step.code || generateDefaultStepFunction(varName, step.type || 'run');
|
|
111
|
+
return { code: cleanStepCode(code), varName };
|
|
112
|
+
}
|
|
113
|
+
// Extract function body from step function code
|
|
114
|
+
export function extractFunctionBody(code) {
|
|
115
|
+
// Find the opening brace after the arrow or function keyword
|
|
116
|
+
const arrowIndex = code.indexOf('=>');
|
|
117
|
+
const functionIndex = code.indexOf('function');
|
|
118
|
+
let startIndex = -1;
|
|
119
|
+
if (arrowIndex !== -1) {
|
|
120
|
+
// Find opening brace after =>
|
|
121
|
+
const openBraceIndex = code.indexOf('{', arrowIndex);
|
|
122
|
+
if (openBraceIndex !== -1) {
|
|
123
|
+
startIndex = openBraceIndex + 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (functionIndex !== -1) {
|
|
127
|
+
// Find opening brace after function declaration
|
|
128
|
+
const openBraceIndex = code.indexOf('{', functionIndex);
|
|
129
|
+
if (openBraceIndex !== -1) {
|
|
130
|
+
startIndex = openBraceIndex + 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (startIndex === -1)
|
|
134
|
+
return null;
|
|
135
|
+
// Find the matching closing brace by counting braces
|
|
136
|
+
let braceCount = 1;
|
|
137
|
+
let endIndex = startIndex;
|
|
138
|
+
while (endIndex < code.length && braceCount > 0) {
|
|
139
|
+
if (code[endIndex] === '{') {
|
|
140
|
+
braceCount++;
|
|
141
|
+
}
|
|
142
|
+
else if (code[endIndex] === '}') {
|
|
143
|
+
braceCount--;
|
|
144
|
+
}
|
|
145
|
+
if (braceCount > 0) {
|
|
146
|
+
endIndex++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (braceCount === 0) {
|
|
150
|
+
return code.substring(startIndex, endIndex).trim();
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
// Transform a step function for execution inside Inngest wrapper
|
|
155
|
+
export function transformStepForExecution(stepFunction, stepType, stepLabel) {
|
|
156
|
+
const functionBody = extractFunctionBody(stepFunction.code);
|
|
157
|
+
if (!functionBody) {
|
|
158
|
+
return `// ${stepLabel}\nconst ${stepFunction.varName}_result = { success: false, error: 'Invalid function format' };`;
|
|
159
|
+
}
|
|
160
|
+
const cleanedBody = cleanStepCode(functionBody);
|
|
161
|
+
// For these step types, execute the body directly (they contain step.* calls)
|
|
162
|
+
if (stepType === 'sleep' || stepType === 'sendEvent' || stepType === 'waitForEvent') {
|
|
163
|
+
// Transform the body to ensure result is captured
|
|
164
|
+
let transformedBody = cleanedBody;
|
|
165
|
+
// Replace step method calls to ensure result variable is created
|
|
166
|
+
transformedBody = transformedBody.replace(/(const\s+\w+\s*=\s*)?await\s+step\.(sleep|sendEvent|waitForEvent)/g, (match, constPart, method) => {
|
|
167
|
+
if (constPart) {
|
|
168
|
+
// Already has assignment, replace variable name
|
|
169
|
+
return `const ${stepFunction.varName}_result = await step.${method}`;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// No assignment, add one
|
|
173
|
+
return `const ${stepFunction.varName}_result = await step.${method}`;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
// Find the original variable name from the step assignment
|
|
177
|
+
const originalVarMatch = transformedBody.match(/const\s+(\w+)\s*=\s*await\s+step\./);
|
|
178
|
+
let originalVarName = null;
|
|
179
|
+
// Also try to find variable from the original code before transformation
|
|
180
|
+
if (!originalVarMatch) {
|
|
181
|
+
const [, codeVarName] = cleanedBody.match(/const\s+(\w+)\s*=\s*await\s+step\./) || [];
|
|
182
|
+
if (codeVarName) {
|
|
183
|
+
originalVarName = codeVarName;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Replace variable references (like eventResult, sleepResult, waitResult)
|
|
187
|
+
// Common patterns: eventResult, sleepResult, waitResult, or any variable used in console.log/return
|
|
188
|
+
if (originalVarName) {
|
|
189
|
+
// Replace all references to the original variable with the new result variable
|
|
190
|
+
const varPattern = new RegExp(`\\b${originalVarName}\\b`, 'g');
|
|
191
|
+
transformedBody = transformedBody.replace(varPattern, `${stepFunction.varName}_result`);
|
|
192
|
+
}
|
|
193
|
+
// Also replace common result variable names that might not have been caught
|
|
194
|
+
transformedBody = transformedBody
|
|
195
|
+
.replace(/\beventResult\b/g, `${stepFunction.varName}_result`)
|
|
196
|
+
.replace(/\bsleepResult\b/g, `${stepFunction.varName}_result`)
|
|
197
|
+
.replace(/\bwaitResult\b/g, `${stepFunction.varName}_result`);
|
|
198
|
+
// Remove return statements (since we'll add our own unified return)
|
|
199
|
+
transformedBody = transformedBody
|
|
200
|
+
.split('\n')
|
|
201
|
+
.filter((line) => {
|
|
202
|
+
const trimmed = line.trim();
|
|
203
|
+
// Remove lines that start with 'return'
|
|
204
|
+
return !trimmed.match(/^return\s/);
|
|
205
|
+
})
|
|
206
|
+
.join('\n');
|
|
207
|
+
return `// ${stepLabel}\n ${transformedBody}`;
|
|
208
|
+
}
|
|
209
|
+
// For other step types, wrap in step.run()
|
|
210
|
+
return `// ${stepLabel}
|
|
211
|
+
const ${stepFunction.varName}_result = await step.run('${stepFunction.varName}', async (event, step) => {
|
|
212
|
+
${cleanedBody}
|
|
213
|
+
});`;
|
|
214
|
+
}
|
|
215
|
+
// Generate complete Inngest function from step functions
|
|
216
|
+
export function generateInngestFunctionFromSteps(stepFunctions) {
|
|
217
|
+
// Transform each step for execution
|
|
218
|
+
const stepBlocks = [];
|
|
219
|
+
const resultVars = [];
|
|
220
|
+
stepFunctions.forEach((stepFunc, index) => {
|
|
221
|
+
const stepType = stepFunc.type || 'run';
|
|
222
|
+
const stepLabel = stepFunc.label || `Step ${index + 1}`;
|
|
223
|
+
const executionCode = transformStepForExecution(stepFunc, stepType, stepLabel);
|
|
224
|
+
stepBlocks.push(executionCode);
|
|
225
|
+
// Determine result variable name
|
|
226
|
+
if (executionCode.includes(`${stepFunc.varName}_result`)) {
|
|
227
|
+
resultVars.push(`${stepFunc.varName}_result`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
// For direct execution without result assignment
|
|
231
|
+
resultVars.push(stepFunc.varName);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
// Generate function body without async wrapper
|
|
235
|
+
const code = `${stepBlocks.join('\n\n')}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
steps: ${stepFunctions.length},
|
|
239
|
+
results: {
|
|
240
|
+
${resultVars.map((v, i) => ` ${stepFunctions[i].varName}: ${v}`).join(',\n')}
|
|
241
|
+
}
|
|
242
|
+
};`;
|
|
243
|
+
return code;
|
|
244
|
+
}
|
|
245
|
+
// Convert ExtractedFunction array to step functions and generate Inngest function
|
|
246
|
+
export function generateFromExtractedFunctions(extractedFunctions) {
|
|
247
|
+
// Convert extracted functions to step function format
|
|
248
|
+
const stepFunctions = extractedFunctions.map((func, index) => {
|
|
249
|
+
const { code, varName } = generateStepFunction(func, index + 1);
|
|
250
|
+
// Detect step type from the code
|
|
251
|
+
let type = 'run';
|
|
252
|
+
if (code.includes('step.sleep('))
|
|
253
|
+
type = 'sleep';
|
|
254
|
+
else if (code.includes('step.sendEvent('))
|
|
255
|
+
type = 'sendEvent';
|
|
256
|
+
else if (code.includes('step.waitForEvent('))
|
|
257
|
+
type = 'waitForEvent';
|
|
258
|
+
return {
|
|
259
|
+
code,
|
|
260
|
+
varName,
|
|
261
|
+
type,
|
|
262
|
+
label: func.name || `Step ${index + 1}`,
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
return generateInngestFunctionFromSteps(stepFunctions);
|
|
266
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type InngestStepType = 'run' | 'sleep' | 'sendEvent' | 'waitForEvent' | 'retrieveData' | 'parallel' | 'validateForm';
|
|
2
|
+
export interface InngestStep {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
type: InngestStepType;
|
|
6
|
+
code?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface InngestFunctionDef {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
events: string[];
|
|
15
|
+
steps: InngestStep[];
|
|
16
|
+
concurrency?: number;
|
|
17
|
+
retries?: number;
|
|
18
|
+
timeout?: string | number;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface ExtractedFunction {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
code?: string;
|
|
25
|
+
extensionId?: string;
|
|
26
|
+
extensionName?: string;
|
|
27
|
+
generatedCode?: string;
|
|
28
|
+
steps?: StepDefinition[];
|
|
29
|
+
originalStepName?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface StepFunction {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
code: string;
|
|
35
|
+
type?: InngestStepType;
|
|
36
|
+
description?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface StepDefinition {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
code: string;
|
|
42
|
+
}
|
|
43
|
+
export interface GeneratedFunctionResult {
|
|
44
|
+
code: string;
|
|
45
|
+
functionId: string;
|
|
46
|
+
stepCount: number;
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adminide-stack/form-builder-core",
|
|
3
|
+
"version": "5.1.4-alpha.43",
|
|
4
|
+
"sideEffects": false,
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"module": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -b",
|
|
10
|
+
"clean": "rimraf lib"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"typescript": "^5.4.0"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"gitHead": "e1bb514cf466e95100493832754f4fc2be6675d8"
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InngestFunctionDef,
|
|
3
|
+
InngestStep,
|
|
4
|
+
ExtractedFunction,
|
|
5
|
+
StepFunction,
|
|
6
|
+
GeneratedFunctionResult,
|
|
7
|
+
} from './types';
|
|
8
|
+
import {
|
|
9
|
+
cleanStepCode,
|
|
10
|
+
extractStepVarName,
|
|
11
|
+
generateFromExtractedFunctions,
|
|
12
|
+
extractFunctionBody,
|
|
13
|
+
} from './stepGenerator';
|
|
14
|
+
|
|
15
|
+
// Re-export utilities for backward compatibility
|
|
16
|
+
export { cleanStepCode, extractStepVarName } from './stepGenerator';
|
|
17
|
+
|
|
18
|
+
// Check if code contains direct step operations (sleep, sendEvent, etc)
|
|
19
|
+
function hasDirectStepOperations(code: string): boolean {
|
|
20
|
+
return /step\.(sleep|sendEvent|waitForEvent|run)\s*\(/.test(code);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getDefaultDirectBody(type: string, id: string, label?: string): string {
|
|
24
|
+
switch (type) {
|
|
25
|
+
case 'sleep':
|
|
26
|
+
return `const ${id}_result = await step.sleep('${id}', '5s');`;
|
|
27
|
+
case 'sendEvent':
|
|
28
|
+
return `const ${id}_result = await step.sendEvent('${id}', { name: 'my.custom.event', data: { source: '${
|
|
29
|
+
label || id
|
|
30
|
+
}', ts: new Date().toISOString() } });`;
|
|
31
|
+
case 'waitForEvent':
|
|
32
|
+
return `const ${id}_result = await step.waitForEvent('${id}', { event: 'my.waited.event', timeout: '60s' });`;
|
|
33
|
+
default:
|
|
34
|
+
return `// not supported`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function indent(s: string, spaces: number): string {
|
|
39
|
+
const pad = ' '.repeat(spaces);
|
|
40
|
+
return s
|
|
41
|
+
.split('\n')
|
|
42
|
+
.map((l) => (l ? pad + l : l))
|
|
43
|
+
.join('\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Simple code formatter
|
|
47
|
+
function formatCode(code: string): string {
|
|
48
|
+
// Basic formatting - in production, you'd use a proper formatter
|
|
49
|
+
return code
|
|
50
|
+
.split('\n')
|
|
51
|
+
.map((line) => line.trimEnd())
|
|
52
|
+
.join('\n')
|
|
53
|
+
.replace(/\n{3,}/g, '\n\n'); // Remove excessive blank lines
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function generateStepBlock(step: InngestStep, position: number): string {
|
|
57
|
+
const stepVar = (step.code || '').match(/const\s+(\w+)\s*=/)?.[1] || `step_${position}`;
|
|
58
|
+
|
|
59
|
+
// If caller supplied code, embed it inside step.run wrapper by default for consistency
|
|
60
|
+
if (step.type === 'sleep' || step.type === 'sendEvent' || step.type === 'waitForEvent') {
|
|
61
|
+
// Execute directly (these typically call step.* helpers)
|
|
62
|
+
const body = extractFunctionBody(step.code || '') || getDefaultDirectBody(step.type, stepVar, step.name);
|
|
63
|
+
return `// ${step.description || step.name || `Step ${position}`}
|
|
64
|
+
${body}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const inner = extractFunctionBody(step.code || '') || 'return { success: true };';
|
|
68
|
+
return `// ${step.description || step.name || `Step ${position}`}
|
|
69
|
+
const ${stepVar}_result = await step.run('${stepVar}', async (event, step) => {
|
|
70
|
+
${indent(inner, 2)}
|
|
71
|
+
});`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Minimal shared generator. Replace internals by moving editor logic here incrementally.
|
|
75
|
+
export function generateFunctionCode(def: InngestFunctionDef): string {
|
|
76
|
+
const eventsString = def.events.length > 0 ? def.events.map((e) => `'${e}'`).join(', ') : `'app.event'`;
|
|
77
|
+
const safeId = def.id.replace(/[^a-zA-Z0-9]/g, '') || 'function';
|
|
78
|
+
const body = generateHandlerBody(def);
|
|
79
|
+
return `const ${safeId}Function = inngest.createFunction(
|
|
80
|
+
{ id: '${def.id}' },
|
|
81
|
+
{ event: [${eventsString}] },
|
|
82
|
+
async ({ event, step }) => {
|
|
83
|
+
${indent(body, 4)}
|
|
84
|
+
}
|
|
85
|
+
);`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// New: return only the handler body that backend executes and editor displays
|
|
89
|
+
export function generateHandlerBody(def: InngestFunctionDef): string {
|
|
90
|
+
const stepBlocks = def.steps.map((step, index) => generateStepBlock(step, index + 1)).join('\n');
|
|
91
|
+
const footer = `return {\n functionId: '${def.id}',\n steps: ${def.steps.length},\n timestamp: new Date().toISOString()\n};`;
|
|
92
|
+
return `${stepBlocks}\n${footer}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Generate Inngest function from database-extracted functions
|
|
96
|
+
export function generateStepFunctionsFromDB(
|
|
97
|
+
functionId: string,
|
|
98
|
+
events: string[],
|
|
99
|
+
extractedFunctions: ExtractedFunction[],
|
|
100
|
+
): GeneratedFunctionResult {
|
|
101
|
+
if (!extractedFunctions || extractedFunctions.length === 0) {
|
|
102
|
+
throw new Error('No functions provided to generate');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Use the new unified generator
|
|
106
|
+
const code = generateFromExtractedFunctions(extractedFunctions);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
code: formatCode(code),
|
|
110
|
+
functionId,
|
|
111
|
+
stepCount: extractedFunctions.length,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Wrap multiple step functions in a single Inngest function
|
|
116
|
+
export function wrapStepsInInngestFunction(functionDef: InngestFunctionDef, stepFunctions: StepFunction[]): string {
|
|
117
|
+
const extractedFunctions: ExtractedFunction[] = stepFunctions.map((sf) => ({
|
|
118
|
+
id: sf.id,
|
|
119
|
+
name: sf.name,
|
|
120
|
+
code: sf.code,
|
|
121
|
+
description: sf.description,
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
const result = generateStepFunctionsFromDB(functionDef.id, functionDef.events, extractedFunctions);
|
|
125
|
+
|
|
126
|
+
return result.code;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Generate a single step from an extracted function
|
|
130
|
+
export function generateStepFromFunction(stepFunction: StepFunction, index: number): string {
|
|
131
|
+
const stepVar = extractStepVarName(stepFunction.code) || `step_${index}`;
|
|
132
|
+
const functionBody = extractFunctionBody(stepFunction.code);
|
|
133
|
+
|
|
134
|
+
if (!functionBody) {
|
|
135
|
+
return `// ${
|
|
136
|
+
stepFunction.name || `Step ${index}`
|
|
137
|
+
}\nconst ${stepVar}_result = { success: false, error: 'Invalid function format' };`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const hasDirectOps = hasDirectStepOperations(stepFunction.code);
|
|
141
|
+
const cleanedBody = cleanStepCode(functionBody);
|
|
142
|
+
|
|
143
|
+
if (hasDirectOps) {
|
|
144
|
+
return `// ${stepFunction.name || `Step ${index}`}\n${cleanedBody}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return `// ${
|
|
148
|
+
stepFunction.name || `Step ${index}`
|
|
149
|
+
}\nconst ${stepVar}_result = await step.run('${stepVar}', async (event, step) => {\n${indent(cleanedBody, 2)}\n});`;
|
|
150
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import type { InngestStep, ExtractedFunction } from './types';
|
|
2
|
+
|
|
3
|
+
// Re-export for convenience
|
|
4
|
+
export type { ExtractedFunction } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Step function generation utilities for unified code generation
|
|
8
|
+
* Used by both browser editor and server execution
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Pass through code without any cleaning/validation
|
|
12
|
+
export function cleanStepCode(code: string): string {
|
|
13
|
+
return code;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Extract step variable name from code
|
|
17
|
+
export function extractStepVarName(code: string): string | null {
|
|
18
|
+
const match = code.match(/const\s+(\w+)\s*=/);
|
|
19
|
+
return match ? match[1] : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Generate default step function for different step types
|
|
23
|
+
export function generateDefaultStepFunction(stepVar: string, stepType: string): string {
|
|
24
|
+
switch (stepType) {
|
|
25
|
+
case 'sleep':
|
|
26
|
+
return `const ${stepVar} = async (event, step) => {
|
|
27
|
+
// Sleep for specified duration
|
|
28
|
+
const sleepResult = await step.sleep('${stepVar}', '5s');
|
|
29
|
+
|
|
30
|
+
console.log('Sleep completed:', sleepResult);
|
|
31
|
+
|
|
32
|
+
return { success: true, duration: '5s', result: sleepResult };
|
|
33
|
+
};`;
|
|
34
|
+
|
|
35
|
+
case 'sendEvent':
|
|
36
|
+
return `const ${stepVar} = async (event, step) => {
|
|
37
|
+
// Send an event to trigger other functions
|
|
38
|
+
const eventResult = await step.sendEvent('${stepVar}', {
|
|
39
|
+
name: 'my.custom.event',
|
|
40
|
+
data: {
|
|
41
|
+
source: '${stepVar}',
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
payload: event.data
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log('Event sent:', eventResult);
|
|
48
|
+
|
|
49
|
+
return { success: true, eventSent: true, result: eventResult };
|
|
50
|
+
};`;
|
|
51
|
+
|
|
52
|
+
case 'waitForEvent':
|
|
53
|
+
return `const ${stepVar} = async (event, step) => {
|
|
54
|
+
// Wait for a specific event before continuing
|
|
55
|
+
const waitResult = await step.waitForEvent('${stepVar}', {
|
|
56
|
+
event: 'my.waited.event',
|
|
57
|
+
timeout: '60s'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
console.log('Event received:', waitResult);
|
|
61
|
+
|
|
62
|
+
return { success: true, eventReceived: true, result: waitResult };
|
|
63
|
+
};`;
|
|
64
|
+
|
|
65
|
+
case 'retrieveData':
|
|
66
|
+
return `const ${stepVar} = async (event, step) => {
|
|
67
|
+
// Data retrieval implementation
|
|
68
|
+
const formData = event.data.formData || {};
|
|
69
|
+
|
|
70
|
+
// Process and retrieve data
|
|
71
|
+
const retrievedData = {
|
|
72
|
+
...formData,
|
|
73
|
+
processedAt: new Date().toISOString()
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return { success: true, data: retrievedData };
|
|
77
|
+
};`;
|
|
78
|
+
|
|
79
|
+
case 'validateForm':
|
|
80
|
+
return `const ${stepVar} = async (event, step) => {
|
|
81
|
+
// Validate form data
|
|
82
|
+
const formData = event.data.formData || {};
|
|
83
|
+
const errors = [];
|
|
84
|
+
|
|
85
|
+
// Add validation logic here
|
|
86
|
+
if (!formData.email) {
|
|
87
|
+
errors.push('Email is required');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
success: errors.length === 0,
|
|
92
|
+
errors,
|
|
93
|
+
validated: errors.length === 0
|
|
94
|
+
};
|
|
95
|
+
};`;
|
|
96
|
+
|
|
97
|
+
case 'parallel':
|
|
98
|
+
return `const ${stepVar} = async (event, step) => {
|
|
99
|
+
// Execute parallel operations
|
|
100
|
+
const results = await Promise.all([
|
|
101
|
+
// Add parallel operations here
|
|
102
|
+
Promise.resolve({ task: 'task1', result: 'completed' }),
|
|
103
|
+
Promise.resolve({ task: 'task2', result: 'completed' })
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
return { success: true, results };
|
|
107
|
+
};`;
|
|
108
|
+
|
|
109
|
+
default:
|
|
110
|
+
return `const ${stepVar} = async (event, step) => {
|
|
111
|
+
// Default step implementation
|
|
112
|
+
return { success: true };
|
|
113
|
+
};`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Generate a step function in editor format (const step_xxx = async (event, step) => {...})
|
|
118
|
+
export function generateStepFunction(
|
|
119
|
+
step: InngestStep | ExtractedFunction,
|
|
120
|
+
index: number,
|
|
121
|
+
): { code: string; varName: string } {
|
|
122
|
+
// Extract or generate step variable name
|
|
123
|
+
const varName =
|
|
124
|
+
extractStepVarName(step.code || '') ||
|
|
125
|
+
(step as ExtractedFunction).originalStepName ||
|
|
126
|
+
step.id ||
|
|
127
|
+
`step_${index}`;
|
|
128
|
+
|
|
129
|
+
// If step has code, use it; otherwise generate default
|
|
130
|
+
const code = step.code || generateDefaultStepFunction(varName, (step as InngestStep).type || 'run');
|
|
131
|
+
|
|
132
|
+
return { code: cleanStepCode(code), varName };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Extract function body from step function code
|
|
136
|
+
export function extractFunctionBody(code: string): string | null {
|
|
137
|
+
// Find the opening brace after the arrow or function keyword
|
|
138
|
+
const arrowIndex = code.indexOf('=>');
|
|
139
|
+
const functionIndex = code.indexOf('function');
|
|
140
|
+
|
|
141
|
+
let startIndex = -1;
|
|
142
|
+
|
|
143
|
+
if (arrowIndex !== -1) {
|
|
144
|
+
// Find opening brace after =>
|
|
145
|
+
const openBraceIndex = code.indexOf('{', arrowIndex);
|
|
146
|
+
if (openBraceIndex !== -1) {
|
|
147
|
+
startIndex = openBraceIndex + 1;
|
|
148
|
+
}
|
|
149
|
+
} else if (functionIndex !== -1) {
|
|
150
|
+
// Find opening brace after function declaration
|
|
151
|
+
const openBraceIndex = code.indexOf('{', functionIndex);
|
|
152
|
+
if (openBraceIndex !== -1) {
|
|
153
|
+
startIndex = openBraceIndex + 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (startIndex === -1) return null;
|
|
158
|
+
|
|
159
|
+
// Find the matching closing brace by counting braces
|
|
160
|
+
let braceCount = 1;
|
|
161
|
+
let endIndex = startIndex;
|
|
162
|
+
while (endIndex < code.length && braceCount > 0) {
|
|
163
|
+
if (code[endIndex] === '{') {
|
|
164
|
+
braceCount++;
|
|
165
|
+
} else if (code[endIndex] === '}') {
|
|
166
|
+
braceCount--;
|
|
167
|
+
}
|
|
168
|
+
if (braceCount > 0) {
|
|
169
|
+
endIndex++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (braceCount === 0) {
|
|
174
|
+
return code.substring(startIndex, endIndex).trim();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Transform a step function for execution inside Inngest wrapper
|
|
181
|
+
export function transformStepForExecution(
|
|
182
|
+
stepFunction: { code: string; varName: string },
|
|
183
|
+
stepType: string,
|
|
184
|
+
stepLabel: string,
|
|
185
|
+
): string {
|
|
186
|
+
const functionBody = extractFunctionBody(stepFunction.code);
|
|
187
|
+
if (!functionBody) {
|
|
188
|
+
return `// ${stepLabel}\nconst ${stepFunction.varName}_result = { success: false, error: 'Invalid function format' };`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const cleanedBody = cleanStepCode(functionBody);
|
|
192
|
+
|
|
193
|
+
// For these step types, execute the body directly (they contain step.* calls)
|
|
194
|
+
if (stepType === 'sleep' || stepType === 'sendEvent' || stepType === 'waitForEvent') {
|
|
195
|
+
// Transform the body to ensure result is captured
|
|
196
|
+
let transformedBody = cleanedBody;
|
|
197
|
+
|
|
198
|
+
// Replace step method calls to ensure result variable is created
|
|
199
|
+
transformedBody = transformedBody.replace(
|
|
200
|
+
/(const\s+\w+\s*=\s*)?await\s+step\.(sleep|sendEvent|waitForEvent)/g,
|
|
201
|
+
(match, constPart, method) => {
|
|
202
|
+
if (constPart) {
|
|
203
|
+
// Already has assignment, replace variable name
|
|
204
|
+
return `const ${stepFunction.varName}_result = await step.${method}`;
|
|
205
|
+
} else {
|
|
206
|
+
// No assignment, add one
|
|
207
|
+
return `const ${stepFunction.varName}_result = await step.${method}`;
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// Find the original variable name from the step assignment
|
|
213
|
+
const originalVarMatch = transformedBody.match(/const\s+(\w+)\s*=\s*await\s+step\./);
|
|
214
|
+
let originalVarName = null;
|
|
215
|
+
|
|
216
|
+
// Also try to find variable from the original code before transformation
|
|
217
|
+
if (!originalVarMatch) {
|
|
218
|
+
const [, codeVarName] = cleanedBody.match(/const\s+(\w+)\s*=\s*await\s+step\./) || [];
|
|
219
|
+
if (codeVarName) {
|
|
220
|
+
originalVarName = codeVarName;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Replace variable references (like eventResult, sleepResult, waitResult)
|
|
225
|
+
// Common patterns: eventResult, sleepResult, waitResult, or any variable used in console.log/return
|
|
226
|
+
if (originalVarName) {
|
|
227
|
+
// Replace all references to the original variable with the new result variable
|
|
228
|
+
const varPattern = new RegExp(`\\b${originalVarName}\\b`, 'g');
|
|
229
|
+
transformedBody = transformedBody.replace(varPattern, `${stepFunction.varName}_result`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Also replace common result variable names that might not have been caught
|
|
233
|
+
transformedBody = transformedBody
|
|
234
|
+
.replace(/\beventResult\b/g, `${stepFunction.varName}_result`)
|
|
235
|
+
.replace(/\bsleepResult\b/g, `${stepFunction.varName}_result`)
|
|
236
|
+
.replace(/\bwaitResult\b/g, `${stepFunction.varName}_result`);
|
|
237
|
+
|
|
238
|
+
// Remove return statements (since we'll add our own unified return)
|
|
239
|
+
transformedBody = transformedBody
|
|
240
|
+
.split('\n')
|
|
241
|
+
.filter((line) => {
|
|
242
|
+
const trimmed = line.trim();
|
|
243
|
+
// Remove lines that start with 'return'
|
|
244
|
+
return !trimmed.match(/^return\s/);
|
|
245
|
+
})
|
|
246
|
+
.join('\n');
|
|
247
|
+
|
|
248
|
+
return `// ${stepLabel}\n ${transformedBody}`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// For other step types, wrap in step.run()
|
|
252
|
+
return `// ${stepLabel}
|
|
253
|
+
const ${stepFunction.varName}_result = await step.run('${stepFunction.varName}', async (event, step) => {
|
|
254
|
+
${cleanedBody}
|
|
255
|
+
});`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Generate complete Inngest function from step functions
|
|
259
|
+
export function generateInngestFunctionFromSteps(
|
|
260
|
+
stepFunctions: Array<{ code: string; varName: string; type?: string; label?: string }>,
|
|
261
|
+
): string {
|
|
262
|
+
// Transform each step for execution
|
|
263
|
+
const stepBlocks: string[] = [];
|
|
264
|
+
const resultVars: string[] = [];
|
|
265
|
+
|
|
266
|
+
stepFunctions.forEach((stepFunc, index) => {
|
|
267
|
+
const stepType = stepFunc.type || 'run';
|
|
268
|
+
const stepLabel = stepFunc.label || `Step ${index + 1}`;
|
|
269
|
+
|
|
270
|
+
const executionCode = transformStepForExecution(stepFunc, stepType, stepLabel);
|
|
271
|
+
stepBlocks.push(executionCode);
|
|
272
|
+
|
|
273
|
+
// Determine result variable name
|
|
274
|
+
if (executionCode.includes(`${stepFunc.varName}_result`)) {
|
|
275
|
+
resultVars.push(`${stepFunc.varName}_result`);
|
|
276
|
+
} else {
|
|
277
|
+
// For direct execution without result assignment
|
|
278
|
+
resultVars.push(stepFunc.varName);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Generate function body without async wrapper
|
|
283
|
+
const code = `${stepBlocks.join('\n\n')}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
steps: ${stepFunctions.length},
|
|
287
|
+
results: {
|
|
288
|
+
${resultVars.map((v, i) => ` ${stepFunctions[i].varName}: ${v}`).join(',\n')}
|
|
289
|
+
}
|
|
290
|
+
};`;
|
|
291
|
+
|
|
292
|
+
return code;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Convert ExtractedFunction array to step functions and generate Inngest function
|
|
296
|
+
export function generateFromExtractedFunctions(extractedFunctions: ExtractedFunction[]): string {
|
|
297
|
+
// Convert extracted functions to step function format
|
|
298
|
+
const stepFunctions = extractedFunctions.map((func, index) => {
|
|
299
|
+
const { code, varName } = generateStepFunction(func, index + 1);
|
|
300
|
+
|
|
301
|
+
// Detect step type from the code
|
|
302
|
+
let type = 'run';
|
|
303
|
+
if (code.includes('step.sleep(')) type = 'sleep';
|
|
304
|
+
else if (code.includes('step.sendEvent(')) type = 'sendEvent';
|
|
305
|
+
else if (code.includes('step.waitForEvent(')) type = 'waitForEvent';
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
code,
|
|
309
|
+
varName,
|
|
310
|
+
type,
|
|
311
|
+
label: func.name || `Step ${index + 1}`,
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return generateInngestFunctionFromSteps(stepFunctions);
|
|
316
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type InngestStepType =
|
|
2
|
+
| 'run'
|
|
3
|
+
| 'sleep'
|
|
4
|
+
| 'sendEvent'
|
|
5
|
+
| 'waitForEvent'
|
|
6
|
+
| 'retrieveData'
|
|
7
|
+
| 'parallel'
|
|
8
|
+
| 'validateForm';
|
|
9
|
+
|
|
10
|
+
export interface InngestStep {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
type: InngestStepType;
|
|
14
|
+
code?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
// Loose optional fields to avoid coupling; extend in callers as needed
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface InngestFunctionDef {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
events: string[];
|
|
25
|
+
steps: InngestStep[];
|
|
26
|
+
concurrency?: number;
|
|
27
|
+
retries?: number;
|
|
28
|
+
timeout?: string | number;
|
|
29
|
+
// Extra passthrough metadata
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// New interfaces for database-extracted functions
|
|
34
|
+
export interface ExtractedFunction {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
code?: string; // The complete async function code
|
|
38
|
+
extensionId?: string;
|
|
39
|
+
extensionName?: string;
|
|
40
|
+
generatedCode?: string;
|
|
41
|
+
steps?: StepDefinition[];
|
|
42
|
+
originalStepName?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface StepFunction {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
code: string;
|
|
49
|
+
type?: InngestStepType;
|
|
50
|
+
description?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface StepDefinition {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
code: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface GeneratedFunctionResult {
|
|
60
|
+
code: string;
|
|
61
|
+
functionId: string;
|
|
62
|
+
stepCount: number;
|
|
63
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/index.ts","./src/inngest/generatefunctioncode.ts","./src/inngest/stepgenerator.ts","./src/inngest/types.ts"],"version":"5.9.2"}
|