@izara_project/izara-core-generate-service-code 1.0.60 → 1.0.62

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 (52) hide show
  1. package/package.json +11 -8
  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 +12 -22
  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 +3 -2
  11. package/src/codeGenerators/app/src/generatedCode/Flow/FlowRbac/RbacFlowGenerator.js +4 -0
  12. package/src/codeGenerators/app/src/generatedCode/Flow/FlowRelationships/RelationshipFlowGenerator.js +2 -1
  13. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowEndpoint_HdrWbs.ejs +2 -2
  14. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowEndpoint_Main.ejs +8 -4
  15. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowMain_Wbs.ejs +4 -4
  16. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/s3/CreatePreSignUrl_Main.ejs +1 -1
  17. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/s3/ProcessS3File_Main.ejs +1 -1
  18. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/step/FlowEndpointComplete_Main.ejs +11 -12
  19. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseDsqHandler.js +55 -0
  20. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseSqsHandler.js +20 -21
  21. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/__tests__/BaseDsqHandler.test.js +93 -0
  22. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/dsq.js +5 -0
  23. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/lambdaSyncApi.js +10 -19
  24. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/lambdaSyncInv.js +10 -19
  25. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/ownTopic.js +16 -4
  26. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/registry.js +3 -1
  27. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrApi.ejs +3 -3
  28. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrDsq.ejs +137 -0
  29. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrInv.ejs +3 -3
  30. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrSqs.ejs +62 -51
  31. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/eventTypes.js +2 -2
  32. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowEntryPointBase.js +8 -4
  33. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowMainFunctionBase.js +28 -36
  34. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowNaming.js +16 -0
  35. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowSelection.js +0 -1
  36. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepBase.js +11 -5
  37. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepNormalizer.js +48 -1
  38. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowValidator.js +7 -21
  39. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/propertyNormalizer.js +20 -0
  40. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/roleNameDetection.js +49 -0
  41. package/src/codeGenerators/app/src/generatedCode/libs/templates/Consts.ejs +10 -10
  42. package/src/codeGenerators/resource/sls_yaml/DynamoDBGenerator.js +1 -10
  43. package/src/codeGenerators/resource/sls_yaml/FlowOutGenerator.js +1 -10
  44. package/src/codeGenerators/resource/sls_yaml/FlowResourceYamlGenerator.js +3 -12
  45. package/src/core/validate.js +9 -5
  46. package/src/generate.js +1 -1
  47. package/src/generateCode.js +90 -24
  48. package/src/schemaGenerators/app/src/schemas/FlowSchemas/FlowSchemaGenerator.js +0 -43
  49. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicFlowSchemaTemplate.ejs +3 -3
  50. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicRbacFlowSchemaTemplate.ejs +1 -1
  51. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/RelationshipFlowSchemaTemplate.ejs +2 -2
  52. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/UserRbacFlowSchemaTemplate.ejs +1 -1
@@ -0,0 +1,137 @@
1
+ /*
2
+ Copyright (C) 2020 Sven Mason <http://izara.io>
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import { middlewareHandler as middleware } from '@izara_project/izara-middleware';
19
+ import { recordHandlerSharedLib } from '@izara_project/izara-core-library-record-handler';
20
+ import Logger from '@izara_project/izara-core-library-logger';
21
+ import callingFlowSharedLib from '@izara_project/izara-core-library-calling-flow';
22
+ import <%= functionName %> from './<%= mainFileName %>.js';
23
+
24
+ // validate event properties in body.Message of sqs event
25
+ let perRecordsValidatorSchema = {
26
+ //(<validatorSchema>)
27
+ <% if (typeof startConfig !== 'undefined' && startConfig !== null && startConfig.properties && startConfig.properties.length > 0) { -%>
28
+ type: 'object',
29
+ properties: {
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(', ') %>]
33
+ <% } -%>
34
+ //(</validatorSchema>)
35
+ };
36
+
37
+ <% if (typeof startConfig !== 'undefined' && startConfig !== null && startConfig.messageAttributes && startConfig.messageAttributes.length > 0) { -%>
38
+ let messageAttributeValidatorSchema = {
39
+ type: 'object',
40
+ properties: {
41
+ <% startConfig.messageAttributes.forEach((prop, index) => { %> '<%- prop.propertyName %>': {
42
+ type: 'object',
43
+ properties: {
44
+ Type: { type: 'string' },
45
+ Value: { type: '<%- prop.type || 'string' %>' }
46
+ },
47
+ required: ['Type', 'Value']
48
+ }<%= index < startConfig.messageAttributes.length - 1 ? ',' : '' %>
49
+ <% }) %> },
50
+ required: [<%- startConfig.messageAttributes.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %>]
51
+ };
52
+ <% } else { -%>
53
+ let messageAttributeValidatorSchema = null;
54
+ <% } -%>
55
+
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
+ }
67
+ }
68
+ })
69
+ );
70
+
71
+ export const main = middleware.wrap(async (event, context) => {
72
+ try {
73
+ let recordPromises = [];
74
+
75
+ // loop each record and send to mainFunction
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
+ passOnProperties.push(
98
+ callingFlowSharedLib.addCallingFlowToPassOnProperties(
99
+ record.body.Message
100
+ )
101
+ );
102
+ //(<additionalParams>)
103
+ //(</additionalParams>)
104
+ record._izContext.logger.debug(
105
+ 'passOnProperties in handler',
106
+ passOnProperties
107
+ );
108
+
109
+ // call recordHandlerSharedLib.recordHandler with 3 parameters and return promise(resolve)
110
+ let recordPromise = recordHandlerSharedLib.recordHandler(
111
+ record, // one record will send to mainFunction
112
+ <%= functionName %>, // mainFunction that need to invoke.
113
+ '<%= queueName %>', // queue name that need to retry or send to dlq
114
+ passOnProperties // all parameters that mainFunction needed.
115
+ );
116
+ record._izContext.logger.debug('after recordPromise in handler');
117
+ recordPromises.push(recordPromise); // push promise to recordPromises
118
+ })
119
+ );
120
+
121
+ Logger.debug('before Promise.all(recordPromises) in handler');
122
+ try {
123
+ // --- main await all promises
124
+ await Promise.all(recordPromises); // await all promises
125
+
126
+ return event.Records; // return all for local testing
127
+ } catch {
128
+ Logger.debug(
129
+ 'Promise.all(recordPromises) in handler threw error (at least one record did no resolve)'
130
+ );
131
+ }
132
+ Logger.debug('after Promise.all(recordPromises) in handler');
133
+ } catch (err) {
134
+ Logger.error('Unhandled Error, LambdaFunctionHdrSqs: ', err);
135
+ throw err;
136
+ }
137
+ });
@@ -5,14 +5,14 @@ import <%= functionName %> from './<%= mainFileName %>.js';
5
5
 
6
6
  const validatorSchema = {
7
7
  //(<validatorSchema>)
8
- <% if (typeof inTagConfig !== 'undefined' && inTagConfig !== null && inTagConfig.properties && inTagConfig.properties.length > 0) { -%>
8
+ <% if (typeof startConfig !== 'undefined' && startConfig !== null && startConfig.properties && startConfig.properties.length > 0) { -%>
9
9
  type: 'object',
10
10
  properties: {
11
- <% inTagConfig.properties.forEach(prop => { -%>
11
+ <% startConfig.properties.forEach(prop => { -%>
12
12
  <%- prop.propertyName %>: { type: '<%- prop.type || 'string' %>' },
13
13
  <% }) -%>
14
14
  },
15
- required: [ <%- inTagConfig.properties.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
15
+ required: [ <%- startConfig.properties.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
16
16
  <% } -%>
17
17
  //(</validatorSchema>)
18
18
  };
@@ -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';
@@ -24,27 +24,30 @@ import <%= functionName %> from './<%= mainFileName %>.js';
24
24
  // validate event properties in body.Message of sqs event
25
25
  let perRecordsValidatorSchema = {
26
26
  //(<validatorSchema>)
27
- <% if (typeof inTagConfig !== 'undefined' && inTagConfig !== null && inTagConfig.properties && inTagConfig.properties.length > 0) { -%>
27
+ <% if (typeof startConfig !== 'undefined' && startConfig !== null && startConfig.properties && startConfig.properties.length > 0) { -%>
28
28
  type: 'object',
29
29
  properties: {
30
- <% inTagConfig.properties.forEach(prop => { -%>
31
- <%- prop.propertyName %>: { type: '<%- prop.type || 'string' %>' },
32
- <% }) -%>
33
- },
34
- required: [ <%- inTagConfig.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
  };
38
36
 
39
- <% if (typeof inTagConfig !== 'undefined' && inTagConfig !== null && inTagConfig.messageAttributes && inTagConfig.messageAttributes.length > 0) { -%>
37
+ <% if (typeof startConfig !== 'undefined' && startConfig !== null && startConfig.messageAttributes && startConfig.messageAttributes.length > 0) { -%>
40
38
  let messageAttributeValidatorSchema = {
41
39
  type: 'object',
42
40
  properties: {
43
- <% inTagConfig.messageAttributes.forEach(prop => { -%>
44
- <%- prop.propertyName %>: { type: '<%- prop.type || 'string' %>' },
45
- <% }) -%>
46
- },
47
- required: [ <%- inTagConfig.messageAttributes.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
41
+ <% startConfig.messageAttributes.forEach((prop, index) => { %> '<%- prop.propertyName %>': {
42
+ type: 'object',
43
+ properties: {
44
+ Type: { type: 'string' },
45
+ Value: { type: '<%- prop.type || 'string' %>' }
46
+ },
47
+ required: ['Type', 'Value']
48
+ }<%= index < startConfig.messageAttributes.length - 1 ? ',' : '' %>
49
+ <% }) %> },
50
+ required: [<%- startConfig.messageAttributes.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %>]
48
51
  };
49
52
  <% } else { -%>
50
53
  let messageAttributeValidatorSchema = null;
@@ -52,57 +55,65 @@ let messageAttributeValidatorSchema = null;
52
55
  middleware.setValidatorSchema(recordHandlerSharedLib.baseValidatorSchema());
53
56
 
54
57
  export const main = middleware.wrap(async (event, context) => {
55
-
56
58
  try {
57
-
58
59
  let recordPromises = [];
59
60
 
60
61
  // loop each record and send to mainFunction
61
- await Promise.all(event.Records.map(async record => { // promise.all for map() function
62
-
63
- let passOnProperties = []
64
- record._izContext.logger.debug('record ReceiveMsgOutHdrSqs', record);
65
-
66
- //validate message (and MessageAttributes)
67
- await recordHandlerSharedLib.validateRecord(
68
- record, // one record will send to mainFunction
69
- "<%= queueName %>", // queue name that need to retry or send to dlq
70
- perRecordsValidatorSchema, // schema for record.Message
71
- messageAttributeValidatorSchema
72
- );
73
-
74
- // add argument (to invoke lambda) to passOnProperties[]
75
- passOnProperties.push(record.body.Message);
76
- passOnProperties.push(callingFlowSharedLib.addCallingFlowToPassOnProperties(record.body.Message));
77
- //(<additionalParams>)
78
- //(</additionalParams>)
79
- record._izContext.logger.debug('passOnProperties in handler', passOnProperties);
80
-
81
- // call recordHandlerSharedLib.recordHandler with 3 parameters and return promise(resolve)
82
- let recordPromise = recordHandlerSharedLib.recordHandler(
83
- record, // one record will send to mainFunction
84
- <%= functionName %>, // mainFunction that need to invoke.
85
- "<%= queueName %>", // queue name that need to retry or send to dlq
86
- passOnProperties, // all parameters that mainFunction needed.
87
- );
88
- record._izContext.logger.debug('after recordPromise in handler');
89
- recordPromises.push(recordPromise); // push promise to recordPromises
90
- }))
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
+ passOnProperties.push(
80
+ callingFlowSharedLib.addCallingFlowToPassOnProperties(
81
+ record.body.Message
82
+ )
83
+ );
84
+ //(<additionalParams>)
85
+ //(</additionalParams>)
86
+ record._izContext.logger.debug(
87
+ 'passOnProperties in handler',
88
+ passOnProperties
89
+ );
90
+
91
+ // call recordHandlerSharedLib.recordHandler with 3 parameters and return promise(resolve)
92
+ let recordPromise = recordHandlerSharedLib.recordHandler(
93
+ record, // one record will send to mainFunction
94
+ <%= functionName %>, // mainFunction that need to invoke.
95
+ '<%= queueName %>', // queue name that need to retry or send to dlq
96
+ passOnProperties // all parameters that mainFunction needed.
97
+ );
98
+ record._izContext.logger.debug('after recordPromise in handler');
99
+ recordPromises.push(recordPromise); // push promise to recordPromises
100
+ })
101
+ );
91
102
 
92
103
  Logger.debug('before Promise.all(recordPromises) in handler');
93
104
  try {
94
105
  // --- main await all promises
95
106
  await Promise.all(recordPromises); // await all promises
96
107
 
97
- return event.Records // return all for local testing
98
-
108
+ return event.Records; // return all for local testing
99
109
  } catch {
100
- Logger.debug('Promise.all(recordPromises) in handler threw error (at least one record did no resolve)');
110
+ Logger.debug(
111
+ 'Promise.all(recordPromises) in handler threw error (at least one record did no resolve)'
112
+ );
101
113
  }
102
114
  Logger.debug('after Promise.all(recordPromises) in handler');
103
-
104
115
  } catch (err) {
105
116
  Logger.error('Unhandled Error, LambdaFunctionHdrSqs: ', err);
106
- throw (err);
117
+ throw err;
107
118
  }
108
119
  });
@@ -2,7 +2,7 @@ export const STEP_EVENT_HANDLER_CONFIG = {
2
2
  sqs: { suffix: 'HdrSqs', template: 'sqs' },
3
3
  ownTopic: { suffix: 'HdrSqs', template: 'sqs' },
4
4
  extTopic: { suffix: 'HdrSqs', template: 'sqs' },
5
- dsq: { suffix: 'HdrDsq', template: 'sqs' },
5
+ dsq: { suffix: 'HdrDsq', template: 'dsq' },
6
6
  lambdaSyncInv: { suffix: 'HdrInv', template: 'inv' },
7
7
  lambdaSyncApi: { suffix: 'HdrApi', template: 'api' }
8
8
  };
@@ -17,7 +17,7 @@ export function getStepEventHandlerConfig(eventType) {
17
17
  };
18
18
  }
19
19
 
20
- export function normalizeEvents(events, fallback = ['sqs']) {
20
+ export function normalizeEvents(events, fallback = ['extTopic']) {
21
21
  if (!events) return [...fallback];
22
22
  return Array.isArray(events) ? [...events] : [events];
23
23
  }
@@ -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);
@@ -18,6 +18,7 @@ export async function generateFlowEntryPoint(allSchemas, options) {
18
18
 
19
19
  const templates = {
20
20
  sqs: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrSqs.ejs'), 'utf-8'),
21
+ dsq: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrDsq.ejs'), 'utf-8'),
21
22
  api: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrApi.ejs'), 'utf-8'),
22
23
  inv: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrInv.ejs'), 'utf-8'),
23
24
  endpointMain: await fs.readFile(path.join(__dirname, '..', '..', 'FlowSchemas', 'templates', 'endpoint', 'FlowEndpoint_Main.ejs'), 'utf-8'),
@@ -40,10 +41,13 @@ export async function generateFlowEntryPoint(allSchemas, options) {
40
41
  const flowOutputDir = path.join(baseOutputDir, subDir, upperCaseFlowTag, 'source');
41
42
  await fs.mkdir(flowOutputDir, { recursive: true });
42
43
 
43
- // NOTE: Changed to Process${upperCaseFlowTag}_Main to match legacy.
44
- const mainFileName = processMainFileName(flowTag);
45
44
  // 2. Generate Handlers based on event array using Registry
46
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);
47
51
 
48
52
  // Use invocationType from generatedCodeConfig if available
49
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 inTagStep = flow.flowSteps && flow.flowSteps.find(
79
- s => s.stepName === 'InTag'
80
- );
81
- let inTagConfig = inTagStep ? { ...inTagStep } : null;
78
+ const startStep = flow.flowSteps && flow.flowSteps.length > 0 ? flow.flowSteps[0] : null;
79
+ let startConfig = startStep ? { ...startStep } : null;
82
80
 
83
- if (inTagConfig) {
84
- if (inTagConfig.properties && Array.isArray(inTagConfig.properties)) {
85
- inTagConfig.properties = inTagConfig.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
- });
81
+ if (startConfig) {
82
+ if (startConfig.properties && Array.isArray(startConfig.properties)) {
83
+ startConfig.properties = startConfig.properties.map(p => normalizePropertyType(p, flow.stepProperties));
91
84
  }
92
- if (inTagConfig.messageAttributes && Array.isArray(inTagConfig.messageAttributes)) {
93
- inTagConfig.messageAttributes = inTagConfig.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
- });
85
+ if (startConfig.messageAttributes && Array.isArray(startConfig.messageAttributes)) {
86
+ startConfig.messageAttributes = startConfig.messageAttributes.map(p => normalizePropertyType(p, flow.stepMessageAttributes));
99
87
  }
100
88
  }
101
89
 
@@ -118,8 +106,9 @@ export async function generateFlowMainFunction(allSchemas, options) {
118
106
  topicArn: topicArn,
119
107
  generateCode: generateCode,
120
108
  flow: flow,
121
- inTagConfig: inTagConfig,
122
- tableName: tableName
109
+ startConfig: startConfig,
110
+ tableName: tableName,
111
+ functionName: functionName
123
112
  });
124
113
  await fs.writeFile(
125
114
  path.join(flowOutputDir, `${mainFileName}.js`),
@@ -127,25 +116,28 @@ 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
- inTagConfig: inTagConfig,
142
- tableName: tableName
131
+ startConfig: startConfig,
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)
@@ -162,7 +154,7 @@ export async function generateFlowMainFunction(allSchemas, options) {
162
154
  upperCaseFlowTag: upperCaseFlowTag,
163
155
  generateCode: generateCode,
164
156
  flow: flow,
165
- inTagConfig: inTagConfig
157
+ startConfig: startConfig
166
158
  });
167
159
 
168
160
  await fs.writeFile(
@@ -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
  }
@@ -1,7 +1,6 @@
1
1
  export function getFlowsForGeneration(allSchemas) {
2
2
  return allSchemas.flowsForGeneration || allSchemas.flows || [];
3
3
  }
4
-
5
4
  export function getGeneratedCodeConfigs(flows, flowTag, options = {}) {
6
5
  return flows
7
6
  .filter(flow => {
@@ -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';
@@ -20,6 +20,7 @@ export async function generateFlowSteps(allSchemas, options) {
20
20
  main: await fs.readFile(path.join(__dirname, '..', '..', 'FlowSchemas', 'templates', 'step', 'FlowStep_Main.ejs'), 'utf-8'),
21
21
  complete: await fs.readFile(path.join(__dirname, '..', '..', 'FlowSchemas', 'templates', 'step', 'FlowEndpointComplete_Main.ejs'), 'utf-8'),
22
22
  sqs: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrSqs.ejs'), 'utf-8'),
23
+ dsq: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrDsq.ejs'), 'utf-8'),
23
24
  api: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrApi.ejs'), 'utf-8'),
24
25
  inv: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrInv.ejs'), 'utf-8'),
25
26
 
@@ -51,9 +52,14 @@ export async function generateFlowSteps(allSchemas, options) {
51
52
 
52
53
  let createdDir = false;
53
54
 
54
- 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];
55
58
  const stepName = stepConfig.stepName;
56
- if (['In', 'InTag', '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;
57
63
 
58
64
  if (!createdDir) {
59
65
  await fs.mkdir(flowOutputDir, { recursive: true });
@@ -78,7 +84,7 @@ export async function generateFlowSteps(allSchemas, options) {
78
84
  functionName: `${flowTag}${stepName}`,
79
85
  };
80
86
 
81
- const isCompleteStep = stepName === 'Complete';
87
+ const isCompleteStep = stepName === 'Complete' && isCrudFlow(flowTag);
82
88
  // Base Step Generation
83
89
  if (isCompleteStep) {
84
90
  templateToUse = templates.complete;
@@ -179,7 +185,7 @@ export async function generateFlowSteps(allSchemas, options) {
179
185
  }
180
186
 
181
187
  // 2. Generate Handlers based on stepConfig.event
182
- const events = normalizeEvents(stepConfig.event, ['sqs']); // Default to sqs if missing
188
+ const events = normalizeEvents(stepConfig.event, ['extTopic']); // Default to extTopic if missing
183
189
  for (const ev of events) {
184
190
  if (ev === 'sns') continue;
185
191
  const handlerConfig = getStepEventHandlerConfig(ev);
@@ -21,8 +21,55 @@ export function hasFlowStep(flowSteps, stepName) {
21
21
  function inferStepName(step) {
22
22
  const reservedKeys = new Set([
23
23
  'event', 'settings', 'plugInHooks', 'properties', 'messageAttributes',
24
- 'returnValues', 'inTag', 'stepName'
24
+ 'returnValues', 'startStep', 'stepName'
25
25
  ]);
26
26
  const key = Object.keys(step).find(item => !reservedKeys.has(item));
27
27
  return typeof step[key] === 'string' ? step[key] : key;
28
28
  }
29
+
30
+ export function normalizeUnifiedFlows(rawFlows) {
31
+ return rawFlows.map(flow => {
32
+ if (!flow.flowSteps) return flow;
33
+
34
+ // Handle both array and object empty cases
35
+ if (Array.isArray(flow.flowSteps) && flow.flowSteps.length === 0) return flow;
36
+ if (!Array.isArray(flow.flowSteps) && Object.keys(flow.flowSteps).length === 0) return flow;
37
+
38
+ const normalizedSteps = normalizeFlowSteps(flow.flowSteps);
39
+
40
+ // Ensure flowSteps is always an Array for the rest of the generation process
41
+ flow.flowSteps = normalizedSteps;
42
+
43
+ // ponytail: root-level event is standard, flowStep[0] has no event.
44
+ if (flow.event) {
45
+ const firstStep = flow.flowSteps[0];
46
+ const needsStartStep = !firstStep || firstStep.event;
47
+
48
+ if (needsStartStep) {
49
+ flow.flowSteps.unshift({
50
+ stepName: 'Start',
51
+ properties: flow.properties || [],
52
+ messageAttributes: flow.messageAttributes || []
53
+ });
54
+ }
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
+ }
70
+ }
71
+ });
72
+
73
+ return flow;
74
+ });
75
+ }