@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 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
@@ -0,0 +1,3 @@
1
+ export * from './inngest/types';
2
+ export * from './inngest/generateFunctionCode';
3
+ export * from './inngest/stepGenerator';
package/lib/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './inngest/types';
2
+ export * from './inngest/generateFunctionCode';
3
+ export * from './inngest/stepGenerator';
@@ -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,3 @@
1
+ export * from './inngest/types';
2
+ export * from './inngest/generateFunctionCode';
3
+ export * from './inngest/stepGenerator';
@@ -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,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2019",
4
+ "module": "ES2020",
5
+ "declaration": true,
6
+ "outDir": "lib",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src"]
13
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/index.ts","./src/inngest/generatefunctioncode.ts","./src/inngest/stepgenerator.ts","./src/inngest/types.ts"],"version":"5.9.2"}