@izara_project/izara-core-generate-service-code 1.0.61 → 1.0.63

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 (44) hide show
  1. package/package.json +5 -6
  2. package/src/codeGenerators/app/initial_setup/InitialSetupGenerator.js +100 -150
  3. package/src/codeGenerators/app/initial_setup/templates/InitialSetup_LambdaRole.ejs +27 -20
  4. package/src/codeGenerators/app/sls_yaml/FunctionYamlGenerator.js +8 -17
  5. package/src/codeGenerators/app/sls_yaml/RoleNameConfigGenerator.js +13 -72
  6. package/src/codeGenerators/app/sls_yaml/_policy/PolicyEmitter.js +3 -2
  7. package/src/codeGenerators/app/sls_yaml/_policy/__tests__/PolicyEmitter.test.js +3 -3
  8. package/src/codeGenerators/app/sls_yaml/_policy/templates/ManagedPolicy_Yaml.ejs +2 -2
  9. package/src/codeGenerators/app/sls_yaml/templates/SharedResource_Yaml.ejs +1 -1
  10. package/src/codeGenerators/app/src/generatedCode/Flow/FlowObjects/EndpointsGenerator.js +1 -1
  11. package/src/codeGenerators/app/src/generatedCode/Flow/FlowObjects/templates/crud/CreateEndpoint_Main.ejs +6 -6
  12. package/src/codeGenerators/app/src/generatedCode/Flow/{FlowSchemas → FlowObjects}/templates/step/FlowEndpointComplete_Main.ejs +78 -69
  13. package/src/codeGenerators/app/src/generatedCode/Flow/FlowRelationships/templates/relationship/ProcessChangeRelationship_Main.ejs +2 -2
  14. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowEndpoint_HdrWbs.ejs +2 -2
  15. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowEndpoint_Main.ejs +8 -4
  16. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowMain_Wbs.ejs +4 -4
  17. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/s3/CreatePreSignUrl_Main.ejs +1 -1
  18. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/s3/ProcessS3File_Main.ejs +1 -1
  19. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseDsqHandler.js +4 -13
  20. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseSqsHandler.js +14 -15
  21. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/lambdaSyncApi.js +4 -13
  22. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/lambdaSyncInv.js +4 -13
  23. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/ownTopic.js +16 -4
  24. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrDsq.ejs +79 -63
  25. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrSqs.ejs +62 -50
  26. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowEntryPointBase.js +7 -4
  27. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowMainFunctionBase.js +23 -33
  28. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowNaming.js +16 -0
  29. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepBase.js +10 -5
  30. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepNormalizer.js +21 -20
  31. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowValidator.js +1 -13
  32. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/propertyNormalizer.js +20 -0
  33. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/roleNameDetection.js +49 -0
  34. package/src/codeGenerators/resource/sls_yaml/DynamoDBGenerator.js +1 -10
  35. package/src/codeGenerators/resource/sls_yaml/FlowOutGenerator.js +2 -10
  36. package/src/codeGenerators/resource/sls_yaml/FlowResourceYamlGenerator.js +2 -11
  37. package/src/core/validate.js +9 -5
  38. package/src/generate.js +2 -2
  39. package/src/generateCode.js +21 -0
  40. package/src/schemaGenerators/app/src/schemas/FlowSchemas/FlowSchemaGenerator.js +0 -43
  41. package/test_schema.js +2 -0
  42. package/test_generate.js +0 -7
  43. package/test_normalize.js +0 -17
  44. /package/src/codeGenerators/app/src/generatedCode/Flow/FlowObjects/templates/{FlowEndpointBeforeLogical_Main.ejs → step/FlowEndpointBeforeLogical_Main.ejs} +0 -0
@@ -15,8 +15,8 @@ You should have received a copy of the GNU Affero General Public License
15
15
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import { middlewareHandler as middleware } from "@izara_project/izara-middleware";
19
- import { recordHandlerSharedLib } from "@izara_project/izara-core-library-record-handler";
18
+ import { middlewareHandler as middleware } from '@izara_project/izara-middleware';
19
+ import { recordHandlerSharedLib } from '@izara_project/izara-core-library-record-handler';
20
20
  import Logger from '@izara_project/izara-core-library-logger';
21
21
  import callingFlowSharedLib from '@izara_project/izara-core-library-calling-flow';
22
22
  import <%= functionName %> from './<%= mainFileName %>.js';
@@ -27,11 +27,9 @@ let perRecordsValidatorSchema = {
27
27
  <% if (typeof startConfig !== 'undefined' && startConfig !== null && startConfig.properties && startConfig.properties.length > 0) { -%>
28
28
  type: 'object',
29
29
  properties: {
30
- <% startConfig.properties.forEach(prop => { -%>
31
- '<%- prop.propertyName %>': { type: '<%- prop.type || 'string' %>' },
32
- <% }) -%>
33
- },
34
- required: [ <%- startConfig.properties.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
30
+ <% startConfig.properties.forEach((prop, index) => { %> <%- prop.propertyName %>: { type: '<%- prop.type || 'string' %>' }<%= index < startConfig.properties.length - 1 ? ',' : '' %>
31
+ <% }) %> },
32
+ required: [<%- startConfig.properties.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %>]
35
33
  <% } -%>
36
34
  //(</validatorSchema>)
37
35
  };
@@ -40,90 +38,108 @@ let perRecordsValidatorSchema = {
40
38
  let messageAttributeValidatorSchema = {
41
39
  type: 'object',
42
40
  properties: {
43
- <% startConfig.messageAttributes.forEach(prop => { -%>
44
- '<%- prop.propertyName %>': {
41
+ <% startConfig.messageAttributes.forEach((prop, index) => { %> '<%- prop.propertyName %>': {
45
42
  type: 'object',
46
43
  properties: {
47
44
  Type: { type: 'string' },
48
45
  Value: { type: '<%- prop.type || 'string' %>' }
49
46
  },
50
47
  required: ['Type', 'Value']
51
- },
52
- <% }) -%>
53
- },
54
- required: [ <%- startConfig.messageAttributes.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
48
+ }<%= index < startConfig.messageAttributes.length - 1 ? ',' : '' %>
49
+ <% }) %> },
50
+ required: [<%- startConfig.messageAttributes.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %>]
55
51
  };
56
52
  <% } else { -%>
57
53
  let messageAttributeValidatorSchema = null;
58
54
  <% } -%>
59
55
 
60
- middleware.setValidatorSchema(recordHandlerSharedLib.baseValidatorSchema(
61
- {
62
- type: "object",
63
- required: ['body', 'messageAttributes'],
64
- properties: {
65
- body: {
66
- type: 'object',
67
- },
68
- messageAttributes: {
69
- type: 'object'
56
+ middleware.setValidatorSchema(
57
+ recordHandlerSharedLib.baseValidatorSchema({
58
+ type: 'object',
59
+ required: ['body', 'messageAttributes'],
60
+ properties: {
61
+ body: {
62
+ type: 'object'
63
+ },
64
+ messageAttributes: {
65
+ type: 'object'
66
+ }
70
67
  }
71
- }
72
- }));
68
+ })
69
+ );
73
70
 
74
71
  export const main = middleware.wrap(async (event, context) => {
75
-
76
72
  try {
77
-
78
73
  let recordPromises = [];
79
74
 
80
75
  // loop each record and send to mainFunction
81
- await Promise.all(event.Records.map(async record => { // promise.all for map() function
82
-
83
- let passOnProperties = []
84
- record = recordHandlerSharedLib.reformatDsqMessage(record._izContext, record);
85
- record._izContext.logger.debug('record ReceiveMsgOutHdrSqs', record);
86
-
87
- //validate message (and MessageAttributes)
88
- await recordHandlerSharedLib.validateRecord(
89
- record, // one record will send to mainFunction
90
- "<%= queueName %>", // queue name that need to retry or send to dlq
91
- perRecordsValidatorSchema, // schema for record.Message
92
- messageAttributeValidatorSchema
93
- );
94
-
95
- // add argument (to invoke lambda) to passOnProperties[]
96
- passOnProperties.push(record.body.Message);
97
- passOnProperties.push(callingFlowSharedLib.addCallingFlowToPassOnProperties(record.body.Message));
98
- //(<additionalParams>)
99
- //(</additionalParams>)
100
- record._izContext.logger.debug('passOnProperties in handler', passOnProperties);
101
-
102
- // call recordHandlerSharedLib.recordHandler with 3 parameters and return promise(resolve)
103
- let recordPromise = recordHandlerSharedLib.recordHandler(
104
- record, // one record will send to mainFunction
105
- <%= functionName %>, // mainFunction that need to invoke.
106
- "<%= queueName %>", // queue name that need to retry or send to dlq
107
- passOnProperties, // all parameters that mainFunction needed.
108
- );
109
- record._izContext.logger.debug('after recordPromise in handler');
110
- recordPromises.push(recordPromise); // push promise to recordPromises
111
- }))
76
+ await Promise.all(
77
+ event.Records.map(async record => {
78
+ // promise.all for map() function
79
+
80
+ let passOnProperties = [];
81
+ record = recordHandlerSharedLib.reformatDsqMessage(
82
+ record._izContext,
83
+ record
84
+ );
85
+ record._izContext.logger.debug('record ReceiveMsgOutHdrSqs', record);
86
+
87
+ //validate message (and MessageAttributes)
88
+ await recordHandlerSharedLib.validateRecord(
89
+ record, // one record will send to mainFunction
90
+ '<%= queueName %>', // queue name that need to retry or send to dlq
91
+ perRecordsValidatorSchema, // schema for record.Message
92
+ messageAttributeValidatorSchema
93
+ );
94
+
95
+ // add argument (to invoke lambda) to passOnProperties[]
96
+ passOnProperties.push(record.body.Message);
97
+ <% if (functionName.endsWith('Complete')) { %>
98
+ passOnProperties.push(
99
+ callingFlowSharedLib.addPassBackPropertiesToPassOnProperties(
100
+ record.body.Message
101
+ )
102
+ );
103
+ <% } else { %>
104
+ passOnProperties.push(
105
+ callingFlowSharedLib.addCallingFlowToPassOnProperties(
106
+ record.body.Message
107
+ )
108
+ );
109
+ <% } %>
110
+ //(<additionalParams>)
111
+ //(</additionalParams>)
112
+ record._izContext.logger.debug(
113
+ 'passOnProperties in handler',
114
+ passOnProperties
115
+ );
116
+
117
+ // call recordHandlerSharedLib.recordHandler with 3 parameters and return promise(resolve)
118
+ let recordPromise = recordHandlerSharedLib.recordHandler(
119
+ record, // one record will send to mainFunction
120
+ <%= functionName %>, // mainFunction that need to invoke.
121
+ '<%= queueName %>', // queue name that need to retry or send to dlq
122
+ passOnProperties // all parameters that mainFunction needed.
123
+ );
124
+ record._izContext.logger.debug('after recordPromise in handler');
125
+ recordPromises.push(recordPromise); // push promise to recordPromises
126
+ })
127
+ );
112
128
 
113
129
  Logger.debug('before Promise.all(recordPromises) in handler');
114
130
  try {
115
131
  // --- main await all promises
116
132
  await Promise.all(recordPromises); // await all promises
117
133
 
118
- return event.Records // return all for local testing
119
-
134
+ return event.Records; // return all for local testing
120
135
  } catch {
121
- Logger.debug('Promise.all(recordPromises) in handler threw error (at least one record did no resolve)');
136
+ Logger.debug(
137
+ 'Promise.all(recordPromises) in handler threw error (at least one record did no resolve)'
138
+ );
122
139
  }
123
140
  Logger.debug('after Promise.all(recordPromises) in handler');
124
-
125
141
  } catch (err) {
126
142
  Logger.error('Unhandled Error, LambdaFunctionHdrSqs: ', err);
127
- throw (err);
143
+ throw err;
128
144
  }
129
145
  });
@@ -15,8 +15,8 @@ You should have received a copy of the GNU Affero General Public License
15
15
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import { middlewareHandler as middleware } from "@izara_project/izara-middleware";
19
- import { recordHandlerSharedLib } from "@izara_project/izara-core-library-record-handler";
18
+ import { middlewareHandler as middleware } from '@izara_project/izara-middleware';
19
+ import { recordHandlerSharedLib } from '@izara_project/izara-core-library-record-handler';
20
20
  import Logger from '@izara_project/izara-core-library-logger';
21
21
  import callingFlowSharedLib from '@izara_project/izara-core-library-calling-flow';
22
22
  import <%= functionName %> from './<%= mainFileName %>.js';
@@ -27,11 +27,9 @@ let perRecordsValidatorSchema = {
27
27
  <% if (typeof startConfig !== 'undefined' && startConfig !== null && startConfig.properties && startConfig.properties.length > 0) { -%>
28
28
  type: 'object',
29
29
  properties: {
30
- <% startConfig.properties.forEach(prop => { -%>
31
- '<%- prop.propertyName %>': { type: '<%- prop.type || 'string' %>' },
32
- <% }) -%>
33
- },
34
- required: [ <%- startConfig.properties.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
30
+ <% startConfig.properties.forEach((prop, index) => { %> <%- prop.propertyName %>: { type: '<%- prop.type || 'string' %>' }<%= index < startConfig.properties.length - 1 ? ',' : '' %>
31
+ <% }) %> },
32
+ required: [<%- startConfig.properties.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %>]
35
33
  <% } -%>
36
34
  //(</validatorSchema>)
37
35
  };
@@ -40,18 +38,16 @@ let perRecordsValidatorSchema = {
40
38
  let messageAttributeValidatorSchema = {
41
39
  type: 'object',
42
40
  properties: {
43
- <% startConfig.messageAttributes.forEach(prop => { -%>
44
- '<%- prop.propertyName %>': {
41
+ <% startConfig.messageAttributes.forEach((prop, index) => { %> '<%- prop.propertyName %>': {
45
42
  type: 'object',
46
43
  properties: {
47
44
  Type: { type: 'string' },
48
45
  Value: { type: '<%- prop.type || 'string' %>' }
49
46
  },
50
47
  required: ['Type', 'Value']
51
- },
52
- <% }) -%>
53
- },
54
- required: [ <%- startConfig.messageAttributes.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
48
+ }<%= index < startConfig.messageAttributes.length - 1 ? ',' : '' %>
49
+ <% }) %> },
50
+ required: [<%- startConfig.messageAttributes.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %>]
55
51
  };
56
52
  <% } else { -%>
57
53
  let messageAttributeValidatorSchema = null;
@@ -59,57 +55,73 @@ let messageAttributeValidatorSchema = null;
59
55
  middleware.setValidatorSchema(recordHandlerSharedLib.baseValidatorSchema());
60
56
 
61
57
  export const main = middleware.wrap(async (event, context) => {
62
-
63
58
  try {
64
-
65
59
  let recordPromises = [];
66
60
 
67
61
  // loop each record and send to mainFunction
68
- await Promise.all(event.Records.map(async record => { // promise.all for map() function
69
-
70
- let passOnProperties = []
71
- record._izContext.logger.debug('record ReceiveMsgOutHdrSqs', record);
72
-
73
- //validate message (and MessageAttributes)
74
- await recordHandlerSharedLib.validateRecord(
75
- record, // one record will send to mainFunction
76
- "<%= queueName %>", // queue name that need to retry or send to dlq
77
- perRecordsValidatorSchema, // schema for record.Message
78
- messageAttributeValidatorSchema
79
- );
80
-
81
- // add argument (to invoke lambda) to passOnProperties[]
82
- passOnProperties.push(record.body.Message);
83
- passOnProperties.push(callingFlowSharedLib.addCallingFlowToPassOnProperties(record.body.Message));
84
- //(<additionalParams>)
85
- //(</additionalParams>)
86
- record._izContext.logger.debug('passOnProperties in handler', passOnProperties);
87
-
88
- // call recordHandlerSharedLib.recordHandler with 3 parameters and return promise(resolve)
89
- let recordPromise = recordHandlerSharedLib.recordHandler(
90
- record, // one record will send to mainFunction
91
- <%= functionName %>, // mainFunction that need to invoke.
92
- "<%= queueName %>", // queue name that need to retry or send to dlq
93
- passOnProperties, // all parameters that mainFunction needed.
94
- );
95
- record._izContext.logger.debug('after recordPromise in handler');
96
- recordPromises.push(recordPromise); // push promise to recordPromises
97
- }))
62
+ await Promise.all(
63
+ event.Records.map(async record => {
64
+ // promise.all for map() function
65
+
66
+ let passOnProperties = [];
67
+ record._izContext.logger.debug('record ReceiveMsgOutHdrSqs', record);
68
+
69
+ //validate message (and MessageAttributes)
70
+ await recordHandlerSharedLib.validateRecord(
71
+ record, // one record will send to mainFunction
72
+ '<%= queueName %>', // queue name that need to retry or send to dlq
73
+ perRecordsValidatorSchema, // schema for record.Message
74
+ messageAttributeValidatorSchema
75
+ );
76
+
77
+ // add argument (to invoke lambda) to passOnProperties[]
78
+ passOnProperties.push(record.body.Message);
79
+ <% if (functionName.endsWith('Complete')) { %>
80
+ passOnProperties.push(
81
+ callingFlowSharedLib.addPassBackPropertiesToPassOnProperties(
82
+ record.body.Message
83
+ )
84
+ );
85
+ <% } else { %>
86
+ passOnProperties.push(
87
+ callingFlowSharedLib.addCallingFlowToPassOnProperties(
88
+ record.body.Message
89
+ )
90
+ );
91
+ <% } %>
92
+ //(<additionalParams>)
93
+ //(</additionalParams>)
94
+ record._izContext.logger.debug(
95
+ 'passOnProperties in handler',
96
+ passOnProperties
97
+ );
98
+
99
+ // call recordHandlerSharedLib.recordHandler with 3 parameters and return promise(resolve)
100
+ let recordPromise = recordHandlerSharedLib.recordHandler(
101
+ record, // one record will send to mainFunction
102
+ <%= functionName %>, // mainFunction that need to invoke.
103
+ '<%= queueName %>', // queue name that need to retry or send to dlq
104
+ passOnProperties // all parameters that mainFunction needed.
105
+ );
106
+ record._izContext.logger.debug('after recordPromise in handler');
107
+ recordPromises.push(recordPromise); // push promise to recordPromises
108
+ })
109
+ );
98
110
 
99
111
  Logger.debug('before Promise.all(recordPromises) in handler');
100
112
  try {
101
113
  // --- main await all promises
102
114
  await Promise.all(recordPromises); // await all promises
103
115
 
104
- return event.Records // return all for local testing
105
-
116
+ return event.Records; // return all for local testing
106
117
  } catch {
107
- Logger.debug('Promise.all(recordPromises) in handler threw error (at least one record did no resolve)');
118
+ Logger.debug(
119
+ 'Promise.all(recordPromises) in handler threw error (at least one record did no resolve)'
120
+ );
108
121
  }
109
122
  Logger.debug('after Promise.all(recordPromises) in handler');
110
-
111
123
  } catch (err) {
112
124
  Logger.error('Unhandled Error, LambdaFunctionHdrSqs: ', err);
113
- throw (err);
125
+ throw err;
114
126
  }
115
127
  });
@@ -4,9 +4,9 @@ import { fileURLToPath } from 'url';
4
4
  import ejs from 'ejs';
5
5
  import { EVENT_HANDLERS } from '../events/registry.js';
6
6
  import { STATUS_HANDLERS } from './registry.js';
7
- import { getFlowOutputSubDir, isRbacFlow, isRelationshipFlow } from './flowClassifier.js';
7
+ import { getFlowOutputSubDir, isCrudFlow, isRbacFlow, isRelationshipFlow } from './flowClassifier.js';
8
8
  import { normalizeEvents } from './eventTypes.js';
9
- import { processMainFileName, upperFirst } from './flowNaming.js';
9
+ import { processMainFileName, ownTopicMainFileName, upperFirst } from './flowNaming.js';
10
10
  import { getFlowsForGeneration, getGeneratedCodeConfigs } from './flowSelection.js';
11
11
 
12
12
  const __filename = fileURLToPath(import.meta.url);
@@ -41,10 +41,13 @@ export async function generateFlowEntryPoint(allSchemas, options) {
41
41
  const flowOutputDir = path.join(baseOutputDir, subDir, upperCaseFlowTag, 'source');
42
42
  await fs.mkdir(flowOutputDir, { recursive: true });
43
43
 
44
- // NOTE: Changed to Process${upperCaseFlowTag}_Main to match legacy.
45
- const mainFileName = processMainFileName(flowTag);
46
44
  // 2. Generate Handlers based on event array using Registry
47
45
  const events = normalizeEvents(flow.event, ['ownTopic']);
46
+
47
+ const isOwnTopic = !isCrudFlow(flowTag) && !isRbacFlow(flowTag) && !isRelationshipFlow(flowTag) && events.includes('ownTopic');
48
+ const mainFileName = isOwnTopic
49
+ ? ownTopicMainFileName(flowTag)
50
+ : processMainFileName(flowTag);
48
51
 
49
52
  // Use invocationType from generatedCodeConfig if available
50
53
  if (flow.generatedCodeConfig && flow.generatedCodeConfig.invocationType) {
@@ -3,8 +3,9 @@ import fs from 'fs/promises';
3
3
  import { fileURLToPath } from 'url';
4
4
  import ejs from 'ejs';
5
5
  import { getFlowOutputSubDir, isCrudFlow, isRbacFlow, isRelationshipFlow } from './flowClassifier.js';
6
- import { processMainFileName, upperFirst } from './flowNaming.js';
6
+ import { processMainFileName, processFunctionName, ownTopicMainFileName, ownTopicFunctionName, ownTopicWebSocketMainFileName, ownTopicWebSocketFunctionName, upperFirst } from './flowNaming.js';
7
7
  import { getFlowsForGeneration, getGeneratedCodeConfigs } from './flowSelection.js';
8
+ import { normalizePropertyType } from './propertyNormalizer.js';
8
9
 
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
  const __dirname = path.dirname(__filename);
@@ -41,11 +42,13 @@ export async function generateFlowMainFunction(allSchemas, options) {
41
42
  const flowOutputDir = path.join(baseOutputDir, subDir, upperCaseFlowTag, 'source');
42
43
  await fs.mkdir(flowOutputDir, { recursive: true });
43
44
 
44
- // For ownTopic flows the SNS-publish file is <FlowTag>_Main.js; others stay Process<FlowTag>_Main.js
45
45
  const isOwnTopic = !isCrud && flow.event && flow.event.includes('ownTopic');
46
46
  const mainFileName = isOwnTopic
47
- ? `${upperCaseFlowTag}_Main`
47
+ ? ownTopicMainFileName(flowTag)
48
48
  : processMainFileName(flowTag);
49
+ const functionName = isOwnTopic
50
+ ? ownTopicFunctionName(flowTag)
51
+ : processFunctionName(flowTag);
49
52
  const topicArn = `${upperCaseFlowTag}_In`;
50
53
 
51
54
  const generateCode = getGeneratedCodeConfigs(flows, flowTag, { includeFlowTag: true });
@@ -55,9 +58,6 @@ export async function generateFlowMainFunction(allSchemas, options) {
55
58
  templateName = `${flowTag}Endpoint_Main.ejs`;
56
59
  } else if (String(flowTag || '').toLowerCase().endsWith('rbacflow')) {
57
60
  templateName = 'FlowRbac_Main.ejs';
58
- } else if (isOwnTopic) {
59
- // Non-CRUD flow with ownTopic: WebSocket entry that fetches flowSchema and publishes to SNS In topic
60
- templateName = 'FlowMain_Wbs.ejs';
61
61
  }
62
62
 
63
63
  // crud templates live under FlowObjects (the only output child that uses them);
@@ -75,27 +75,15 @@ export async function generateFlowMainFunction(allSchemas, options) {
75
75
  'utf-8'
76
76
  );
77
77
 
78
- const startStep = flow.flowSteps && flow.flowSteps.find(
79
- s => s.stepName === 'start'
80
- );
78
+ const startStep = flow.flowSteps && flow.flowSteps.length > 0 ? flow.flowSteps[0] : null;
81
79
  let startConfig = startStep ? { ...startStep } : null;
82
80
 
83
81
  if (startConfig) {
84
82
  if (startConfig.properties && Array.isArray(startConfig.properties)) {
85
- startConfig.properties = startConfig.properties.map(p => {
86
- if (typeof p === 'string') {
87
- return (flow.stepProperties && flow.stepProperties[p]) ? flow.stepProperties[p] : { propertyName: p, type: 'string' };
88
- }
89
- return p;
90
- });
83
+ startConfig.properties = startConfig.properties.map(p => normalizePropertyType(p, flow.stepProperties));
91
84
  }
92
85
  if (startConfig.messageAttributes && Array.isArray(startConfig.messageAttributes)) {
93
- startConfig.messageAttributes = startConfig.messageAttributes.map(p => {
94
- if (typeof p === 'string') {
95
- return (flow.stepMessageAttributes && flow.stepMessageAttributes[p]) ? flow.stepMessageAttributes[p] : { propertyName: p, type: 'string' };
96
- }
97
- return p;
98
- });
86
+ startConfig.messageAttributes = startConfig.messageAttributes.map(p => normalizePropertyType(p, flow.stepMessageAttributes));
99
87
  }
100
88
  }
101
89
 
@@ -119,7 +107,8 @@ export async function generateFlowMainFunction(allSchemas, options) {
119
107
  generateCode: generateCode,
120
108
  flow: flow,
121
109
  startConfig: startConfig,
122
- tableName: tableName
110
+ tableName: tableName,
111
+ functionName: functionName
123
112
  });
124
113
  await fs.writeFile(
125
114
  path.join(flowOutputDir, `${mainFileName}.js`),
@@ -127,34 +116,35 @@ export async function generateFlowMainFunction(allSchemas, options) {
127
116
  'utf-8'
128
117
  );
129
118
 
130
- // For ownTopic flows: also generate Process${FlowTag}_Main.js (simple endpoint, triggered from SQS)
119
+ // For ownTopic flows: generate WebSocket main that publishes to SNS In topic
131
120
  if (isOwnTopic) {
132
- const processMainTemplateStr = await fs.readFile(
133
- path.join(__dirname, '..', '..', 'FlowSchemas', 'templates', 'endpoint', 'FlowEndpoint_Main.ejs'),
121
+ const wbsMainTemplateStr = await fs.readFile(
122
+ path.join(__dirname, '..', '..', 'FlowSchemas', 'templates', 'endpoint', 'FlowMain_Wbs.ejs'),
134
123
  'utf-8'
135
124
  );
136
- const processMainContent = ejs.render(processMainTemplateStr, {
125
+ const wbsMainContent = ejs.render(wbsMainTemplateStr, {
137
126
  flowTag: flowTag,
138
127
  upperCaseFlowTag: upperCaseFlowTag,
128
+ topicArn: topicArn,
139
129
  generateCode: generateCode,
140
130
  flow: flow,
141
131
  startConfig: startConfig,
142
- tableName: tableName
132
+ tableName: tableName,
133
+ functionName: ownTopicWebSocketFunctionName(flowTag)
143
134
  });
144
135
  await fs.writeFile(
145
- path.join(flowOutputDir, `${processMainFileName(flowTag)}.js`),
146
- processMainContent,
136
+ path.join(flowOutputDir, `${ownTopicWebSocketMainFileName(flowTag)}.js`),
137
+ wbsMainContent,
147
138
  'utf-8'
148
139
  );
140
+ generatedCount++;
149
141
  }
150
142
 
151
143
  // 2. Generate BeforeLogical Main Logic File if needed (CRUD flows only)
152
144
  const beforeLogicalConfigs = generateCode.filter(c => c.codeHookTag === 'beforeLogical');
153
145
  if (isCrud && beforeLogicalConfigs.length > 0) {
154
- const beforeLogicalTemplateStr = await fs.readFile(
155
- path.join(__dirname, '..', '..', 'FlowObjects', 'templates', 'FlowEndpointBeforeLogical_Main.ejs'),
156
- 'utf-8'
157
- );
146
+ const beforeLogicalTemplatePath = path.join(__dirname, '..', '..', 'FlowObjects', 'templates', 'step', 'FlowEndpointBeforeLogical_Main.ejs');
147
+ const beforeLogicalTemplateStr = await fs.readFile(beforeLogicalTemplatePath, 'utf-8');
158
148
 
159
149
  const beforeLogicalFileName = `${upperCaseFlowTag}BeforeLogical_Main`;
160
150
  const beforeLogicalContent = ejs.render(beforeLogicalTemplateStr, {
@@ -16,6 +16,22 @@ export function processFunctionName(flowTag) {
16
16
  return `Process${upperFirst(flowTag)}`;
17
17
  }
18
18
 
19
+ export function ownTopicMainFileName(flowTag) {
20
+ return `${upperFirst(flowTag)}_Main`;
21
+ }
22
+
23
+ export function ownTopicFunctionName(flowTag) {
24
+ return `${upperFirst(flowTag)}_Main`;
25
+ }
26
+
27
+ export function ownTopicWebSocketMainFileName(flowTag) {
28
+ return `${upperFirst(flowTag)}_WbsMain`;
29
+ }
30
+
31
+ export function ownTopicWebSocketFunctionName(flowTag) {
32
+ return `${upperFirst(flowTag)}_WbsMain`;
33
+ }
34
+
19
35
  export function flowSourceLocation(subDir, flowTag) {
20
36
  return `src/generatedCode/${subDir}/${upperFirst(flowTag)}/source/`;
21
37
  }
@@ -2,7 +2,7 @@ import path from 'path';
2
2
  import fs from 'fs/promises';
3
3
  import { fileURLToPath } from 'url';
4
4
  import ejs from 'ejs';
5
- import { getFlowOutputSubDir, isRbacFlow, isRelationshipFlow } from './flowClassifier.js';
5
+ import { getFlowOutputSubDir, isRbacFlow, isRelationshipFlow, isCrudFlow } from './flowClassifier.js';
6
6
  import { getStepEventHandlerConfig, normalizeEvents } from './eventTypes.js';
7
7
  import { normalizeFlowSteps } from './flowStepNormalizer.js';
8
8
  import { getFlowsForGeneration } from './flowSelection.js';
@@ -18,7 +18,7 @@ export async function generateFlowSteps(allSchemas, options) {
18
18
  // Read templates
19
19
  const templates = {
20
20
  main: await fs.readFile(path.join(__dirname, '..', '..', 'FlowSchemas', 'templates', 'step', 'FlowStep_Main.ejs'), 'utf-8'),
21
- complete: await fs.readFile(path.join(__dirname, '..', '..', 'FlowSchemas', 'templates', 'step', 'FlowEndpointComplete_Main.ejs'), 'utf-8'),
21
+ complete: await fs.readFile(path.join(__dirname, '..', '..', 'FlowObjects', 'templates', 'step', 'FlowEndpointComplete_Main.ejs'), 'utf-8'),
22
22
  sqs: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrSqs.ejs'), 'utf-8'),
23
23
  dsq: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrDsq.ejs'), 'utf-8'),
24
24
  api: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrApi.ejs'), 'utf-8'),
@@ -52,9 +52,14 @@ export async function generateFlowSteps(allSchemas, options) {
52
52
 
53
53
  let createdDir = false;
54
54
 
55
- for (const stepConfig of normalizeFlowSteps(flow.flowSteps)) {
55
+ const normalizedSteps = normalizeFlowSteps(flow.flowSteps);
56
+ for (let index = 0; index < normalizedSteps.length; index++) {
57
+ const stepConfig = normalizedSteps[index];
56
58
  const stepName = stepConfig.stepName;
57
- if (['In', 'start', 'Out', 'beforeLogical'].includes(stepName)) continue;
59
+
60
+ // Skip the first step (which is the entry point, handled by flowMainFunctionBase)
61
+ // Also skip traditional skipped steps by name for backward compatibility
62
+ if (index === 0 || ['in', 'start', 'out', 'beforelogical'].includes(stepName?.toLowerCase())) continue;
58
63
 
59
64
  if (!createdDir) {
60
65
  await fs.mkdir(flowOutputDir, { recursive: true });
@@ -79,7 +84,7 @@ export async function generateFlowSteps(allSchemas, options) {
79
84
  functionName: `${flowTag}${stepName}`,
80
85
  };
81
86
 
82
- const isCompleteStep = stepName === 'Complete';
87
+ const isCompleteStep = stepName === 'Complete' && isCrudFlow(flowTag);
83
88
  // Base Step Generation
84
89
  if (isCompleteStep) {
85
90
  templateToUse = templates.complete;
@@ -40,35 +40,36 @@ export function normalizeUnifiedFlows(rawFlows) {
40
40
  // Ensure flowSteps is always an Array for the rest of the generation process
41
41
  flow.flowSteps = normalizedSteps;
42
42
 
43
- const hasStart = flow.flowSteps.some(s => s.stepName?.toLowerCase() === 'start');
44
-
43
+ // ponytail: root-level event is standard, flowStep[0] has no event.
45
44
  if (flow.event) {
46
- if (!hasStart) {
45
+ const firstStep = flow.flowSteps[0];
46
+ const needsStartStep = !firstStep || firstStep.event;
47
+
48
+ if (needsStartStep) {
47
49
  flow.flowSteps.unshift({
48
50
  stepName: 'Start',
49
51
  properties: flow.properties || [],
50
52
  messageAttributes: flow.messageAttributes || []
51
53
  });
52
54
  }
53
- } else {
54
- const firstStep = flow.flowSteps[0];
55
-
56
- if (firstStep) {
57
- flow.event = firstStep.event;
58
- if (firstStep.generatedCodeConfig) flow.generatedCodeConfig = firstStep.generatedCodeConfig;
59
- if (firstStep.schedules) flow.schedules = firstStep.schedules;
60
- if (firstStep.bucketName) flow.bucketName = firstStep.bucketName;
61
- if (firstStep.createBucket !== undefined) flow.createBucket = firstStep.createBucket;
62
-
63
- if (!hasStart) {
64
- flow.flowSteps.unshift({
65
- stepName: 'Start',
66
- properties: firstStep.properties || [],
67
- messageAttributes: firstStep.messageAttributes || []
68
- });
55
+ }
56
+ // Validate flowStep events
57
+ flow.flowSteps.forEach((step, index) => {
58
+ if (index === 0) {
59
+ if (step.event) {
60
+ throw new Error(`[Schema Error] Flow '${flow.flowTag || 'Unknown'}' -> stepName '${step.stepName}' (Index 0) must not have an 'event'. Set it at the flow root instead.`);
61
+ }
62
+ } else {
63
+ if (step.event) {
64
+ const validStepEvents = ['extTopic', 'lambdaSyncInv', 'dsq'];
65
+ const invalidEvents = step.event.filter(e => !validStepEvents.includes(e));
66
+ if (invalidEvents.length > 0) {
67
+ throw new Error(`[Schema Error] Flow '${flow.flowTag || 'Unknown'}' -> stepName '${step.stepName}' has invalid event: ${invalidEvents.join(', ')}. Allowed: ${validStepEvents.join(', ')}`);
68
+ }
69
69
  }
70
70
  }
71
- }
71
+ });
72
+
72
73
  return flow;
73
74
  });
74
75
  }