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

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 (33) hide show
  1. package/package.json +7 -3
  2. package/src/codeGenerators/app/initial_setup/InitialSetupGenerator.js +7 -7
  3. package/src/codeGenerators/app/sls_yaml/FunctionYamlGenerator.js +4 -5
  4. package/src/codeGenerators/app/src/generatedCode/Flow/FlowObjects/EndpointsGenerator.js +3 -2
  5. package/src/codeGenerators/app/src/generatedCode/Flow/FlowRbac/RbacFlowGenerator.js +4 -0
  6. package/src/codeGenerators/app/src/generatedCode/Flow/FlowRelationships/RelationshipFlowGenerator.js +2 -1
  7. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseDsqHandler.js +64 -0
  8. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseSqsHandler.js +9 -9
  9. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/__tests__/BaseDsqHandler.test.js +93 -0
  10. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/dsq.js +5 -0
  11. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/lambdaSyncApi.js +9 -9
  12. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/lambdaSyncInv.js +9 -9
  13. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/registry.js +3 -1
  14. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrApi.ejs +3 -3
  15. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrDsq.ejs +129 -0
  16. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrInv.ejs +3 -3
  17. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrSqs.ejs +15 -8
  18. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/eventTypes.js +2 -2
  19. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowEntryPointBase.js +1 -0
  20. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowMainFunctionBase.js +11 -11
  21. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowSelection.js +0 -1
  22. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepBase.js +3 -2
  23. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepNormalizer.js +47 -1
  24. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowValidator.js +13 -15
  25. package/src/codeGenerators/app/src/generatedCode/libs/templates/Consts.ejs +10 -10
  26. package/src/codeGenerators/resource/sls_yaml/FlowResourceYamlGenerator.js +2 -2
  27. package/src/generateCode.js +69 -24
  28. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicFlowSchemaTemplate.ejs +3 -3
  29. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicRbacFlowSchemaTemplate.ejs +1 -1
  30. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/RelationshipFlowSchemaTemplate.ejs +2 -2
  31. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/UserRbacFlowSchemaTemplate.ejs +1 -1
  32. package/test_generate.js +7 -0
  33. package/test_normalize.js +17 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izara_project/izara-core-generate-service-code",
3
- "version": "1.0.60",
3
+ "version": "1.0.61",
4
4
  "description": "Code for locally generating per service files",
5
5
  "homepage": "https://bitbucket.org/izara-core-support-services/izara-core-support-services-generate-service-code#readme",
6
6
  "repository": {
@@ -22,15 +22,19 @@
22
22
  "@izara_project/izara-core-library-core": "^1.0.32",
23
23
  "@izara_project/izara-core-library-logger": "^1.0.9",
24
24
  "@izara_project/izara-core-library-s3": "^1.0.10",
25
- "@izara_project/izara-core-library-service-schemas": "^1.0.155",
26
25
  "@izara_project/izara-property-nodes": "^1.0.16",
27
26
  "@izara_project/izara-shared-core": "^1.0.12",
28
- "@izara_project/izara-shared-service-schemas": "^1.0.64",
29
27
  "ejs": "^5.0.1",
30
28
  "js-beautify": "^1.15.4",
31
29
  "yaml": "^2.9.0"
32
30
  },
31
+ "peerDependencies": {
32
+ "@izara_project/izara-core-library-service-schemas": "^1.0.157",
33
+ "@izara_project/izara-shared-service-schemas": "^1.0.65"
34
+ },
33
35
  "devDependencies": {
36
+ "@izara_project/izara-core-library-service-schemas": "^1.0.157",
37
+ "@izara_project/izara-shared-service-schemas": "^1.0.65",
34
38
  "jest": "^30.4.2"
35
39
  }
36
40
  }
@@ -98,37 +98,37 @@ export async function generateInitialSetup(allSchemas, options) {
98
98
  {
99
99
  serviceTag: 'GraphHandler',
100
100
  topicName: 'UpdateRelationship_Out',
101
- sqsEndpoint: 'UpdateRelationshipComplete'
101
+ sqsEndpoint: 'UpdateRelationshipCompleteHdrSqs'
102
102
  },
103
103
  {
104
104
  serviceTag: 'GraphHandler',
105
105
  topicName: 'CreateRelationship_Out',
106
- sqsEndpoint: 'CreateRelationshipComplete'
106
+ sqsEndpoint: 'CreateRelationshipCompleteHdrSqs'
107
107
  },
108
108
  {
109
109
  serviceTag: 'GraphHandler',
110
110
  topicName: 'DeleteRelationship_Out',
111
- sqsEndpoint: 'DeleteRelationshipComplete'
111
+ sqsEndpoint: 'DeleteRelationshipCompleteHdrSqs'
112
112
  },
113
113
  {
114
114
  serviceTag: 'GraphHandler',
115
115
  topicName: 'UpdateNode_Out',
116
- sqsEndpoint: 'UpdateComplete'
116
+ sqsEndpoint: 'UpdateCompleteHdrSqs'
117
117
  },
118
118
  {
119
119
  serviceTag: 'GraphHandler',
120
120
  topicName: 'CreateNode_Out',
121
- sqsEndpoint: 'CreateComplete'
121
+ sqsEndpoint: 'CreateCompleteHdrSqs'
122
122
  },
123
123
  {
124
124
  serviceTag: 'GraphHandler',
125
125
  topicName: 'ChangeRelationshipType_Out',
126
- sqsEndpoint: 'ChangeRelationshipComplete'
126
+ sqsEndpoint: 'ChangeRelationshipCompleteHdrSqs'
127
127
  },
128
128
  {
129
129
  serviceTag: 'GraphHandler',
130
130
  topicName: 'MoveRelationship_Out',
131
- sqsEndpoint: 'MoveRelationshipComplete'
131
+ sqsEndpoint: 'MoveRelationshipCompleteHdrSqs'
132
132
  }
133
133
  ];
134
134
  const snsTpl = await fs.readFile(
@@ -163,15 +163,14 @@ export async function generateFunctionYaml(allSchemas, options) {
163
163
  functionNameConfig = `${upperCase(flowTag).replace('Relationship', 'Rel')}${suffixType}`;
164
164
  }
165
165
  if (handlerBaseName.endsWith('Complete')) {
166
- // e.g. CreateObjectComp or CreateCompleteSqs
166
+ // e.g. CreateCompleteHdrSqs or CreateCompleteSqs
167
167
  if (isCrud) {
168
- // Legacy: DeleteNodeComp, UpdateNodeComp, CreateObjectComp
168
+ // Legacy: DeleteCompleteHdrSqs, UpdateCompleteHdrSqs, CreateCompleteHdrSqs
169
169
  const crudType = upperCase(flowTag);
170
- if (crudType === 'Create') functionNameConfig = `CreateObjectComp`;
171
- else functionNameConfig = `${crudType}NodeComp`;
170
+ functionNameConfig = `${crudType}CompleteHdrSqs`;
172
171
  } else if (isRel) {
173
172
  // Legacy: UpdateRelComp
174
- functionNameConfig = `${upperCase(flowTag).replace('Relationship', 'Rel')}Comp`;
173
+ functionNameConfig = `${upperCase(flowTag)}CompleteHdrSqs`;
175
174
  } else {
176
175
  functionNameConfig = `${handlerBaseName}${suffixType}`;
177
176
  }
@@ -27,6 +27,7 @@ export async function generateEndpointsFlow(allSchemas, options) {
27
27
 
28
28
  const sharedTemplates = {
29
29
  sqs: await fs.readFile(path.join(__dirname, '..', '_shared', 'handlers', 'FlowHandler_HdrSqs.ejs'), 'utf-8'),
30
+ dsq: await fs.readFile(path.join(__dirname, '..', '_shared', 'handlers', 'FlowHandler_HdrDsq.ejs'), 'utf-8'),
30
31
  api: await fs.readFile(path.join(__dirname, '..', '_shared', 'handlers', 'FlowHandler_HdrApi.ejs'), 'utf-8'),
31
32
  inv: await fs.readFile(path.join(__dirname, '..', '_shared', 'handlers', 'FlowHandler_HdrInv.ejs'), 'utf-8'),
32
33
  wbsMain: await fs.readFile(path.join(__dirname, '..', 'FlowSchemas', 'templates', 'endpoint', 'FlowMain_Wbs.ejs'), 'utf-8').catch(() => ''),
@@ -70,7 +71,7 @@ export async function generateEndpointsFlow(allSchemas, options) {
70
71
  topicArn: `${upperAction}_In`,
71
72
  generateCode: generateCode,
72
73
  flow: mockFlow,
73
- inTagConfig: null
74
+ startConfig: null
74
75
  });
75
76
  await fs.writeFile(path.join(flowOutputDir, `${mainFileName}.js`), mainContent, 'utf-8');
76
77
  generatedCount++;
@@ -88,7 +89,7 @@ export async function generateEndpointsFlow(allSchemas, options) {
88
89
  upperCaseFlowTag: upperAction,
89
90
  generateCode: generateCode,
90
91
  flow: mockFlow,
91
- inTagConfig: null
92
+ startConfig: null
92
93
  });
93
94
  await fs.writeFile(path.join(flowOutputDir, `${beforeLogicalFileName}.js`), beforeLogicalContent, 'utf-8');
94
95
  generatedCount++;
@@ -35,6 +35,10 @@ export async function generateRbacFlows(allSchemas, options) {
35
35
  path.join(templateDir, '..', '..', '_shared', 'handlers', 'FlowHandler_HdrSqs.ejs'),
36
36
  'utf-8'
37
37
  ),
38
+ dsq: await fs.readFile(
39
+ path.join(templateDir, '..', '..', '_shared', 'handlers', 'FlowHandler_HdrDsq.ejs'),
40
+ 'utf-8'
41
+ ),
38
42
  api: await fs.readFile(
39
43
  path.join(templateDir, '..', '..', '_shared', 'handlers', 'FlowHandler_HdrApi.ejs'),
40
44
  'utf-8'
@@ -25,6 +25,7 @@ export async function generateRelationshipFlows(allSchemas, options) {
25
25
  // Read standard handler templates for reuse
26
26
  const templates = {
27
27
  sqs: await fs.readFile(path.join(templateDir, '..', '..', '_shared', 'handlers', 'FlowHandler_HdrSqs.ejs'), 'utf-8'),
28
+ dsq: await fs.readFile(path.join(templateDir, '..', '..', '_shared', 'handlers', 'FlowHandler_HdrDsq.ejs'), 'utf-8'),
28
29
  api: await fs.readFile(path.join(templateDir, '..', '..', '_shared', 'handlers', 'FlowHandler_HdrApi.ejs'), 'utf-8'),
29
30
  inv: await fs.readFile(path.join(templateDir, '..', '..', '_shared', 'handlers', 'FlowHandler_HdrInv.ejs'), 'utf-8')
30
31
  };
@@ -98,7 +99,7 @@ export async function generateRelationshipFlows(allSchemas, options) {
98
99
  generatedCount++;
99
100
  } else if (event === 'dsq') {
100
101
  handlerFunctionName = `${processFunction}_HdrDsq`;
101
- handlerContent = ejs.render(templates.sqs, {
102
+ handlerContent = ejs.render(templates.dsq || templates.sqs, {
102
103
  flowTag: flowTag,
103
104
  flowStepName: '',
104
105
  mainFileName: actionFileName,
@@ -0,0 +1,64 @@
1
+ import ejs from 'ejs';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ export async function generateDsqHandler(flow, stepName, suffix, flowOutputDir, mainFileName, templates) {
6
+ const upperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1);
7
+ const flowTag = flow.flowTag;
8
+
9
+ // For entry point stepName is empty
10
+ const handlerFileName = stepName ? `${flowTag}${stepName}_${suffix}` : `Process${upperCase(flowTag)}_${suffix}`;
11
+
12
+ // Function name: matches default export in the referenced _Main.js
13
+ const functionName = stepName ? `${flowTag}${stepName}` : `Process${upperCase(flowTag)}`;
14
+
15
+ // Queue name: keep the full handler suffix so handler code matches
16
+ const queueNameSuffix = suffix;
17
+ let queueName = '';
18
+ if (stepName) {
19
+ queueName = `${flowTag}${stepName}${queueNameSuffix}`;
20
+ } else {
21
+ if (flow.event && flow.event.includes('ownTopic')) {
22
+ queueName = `${flowTag}${queueNameSuffix}`;
23
+ } else {
24
+ queueName = `${functionName}${queueNameSuffix}`;
25
+ }
26
+ }
27
+
28
+ let startConfig = null;
29
+ if (!stepName && flow.flowSteps) {
30
+ const startStep = flow.flowSteps.find(s => s.stepName === 'start');
31
+ if (startStep) {
32
+ startConfig = { ...startStep };
33
+ // Map properties
34
+ if (startConfig.properties && Array.isArray(startConfig.properties)) {
35
+ startConfig.properties = startConfig.properties.map(p => {
36
+ if (typeof p === 'string') {
37
+ return (flow.stepProperties && flow.stepProperties[p]) ? flow.stepProperties[p] : { propertyName: p, type: 'string' };
38
+ }
39
+ return p;
40
+ });
41
+ }
42
+ // Map messageAttributes
43
+ if (startConfig.messageAttributes && Array.isArray(startConfig.messageAttributes)) {
44
+ startConfig.messageAttributes = startConfig.messageAttributes.map(p => {
45
+ if (typeof p === 'string') {
46
+ return (flow.stepMessageAttributes && flow.stepMessageAttributes[p]) ? flow.stepMessageAttributes[p] : { propertyName: p, type: 'string' };
47
+ }
48
+ return p;
49
+ });
50
+ }
51
+ }
52
+ }
53
+
54
+ const handlerContent = ejs.render(templates.dsq || templates.sqs, {
55
+ flowTag: flowTag,
56
+ flowStepName: stepName || '',
57
+ mainFileName: mainFileName,
58
+ functionName: functionName,
59
+ queueName: queueName,
60
+ startConfig: startConfig
61
+ });
62
+
63
+ await fs.writeFile(path.join(flowOutputDir, `${handlerFileName}.js`), handlerContent, 'utf-8');
64
+ }
@@ -28,14 +28,14 @@ export async function generateSqsHandler(flow, stepName, suffix, flowOutputDir,
28
28
  }
29
29
  }
30
30
 
31
- let inTagConfig = null;
31
+ let startConfig = null;
32
32
  if (!stepName && flow.flowSteps) {
33
- const inTagStep = flow.flowSteps.find(s => s.stepName === 'InTag');
34
- if (inTagStep) {
35
- inTagConfig = { ...inTagStep };
33
+ const startStep = flow.flowSteps.find(s => s.stepName === 'start');
34
+ if (startStep) {
35
+ startConfig = { ...startStep };
36
36
  // Map properties
37
- if (inTagConfig.properties && Array.isArray(inTagConfig.properties)) {
38
- inTagConfig.properties = inTagConfig.properties.map(p => {
37
+ if (startConfig.properties && Array.isArray(startConfig.properties)) {
38
+ startConfig.properties = startConfig.properties.map(p => {
39
39
  if (typeof p === 'string') {
40
40
  return (flow.stepProperties && flow.stepProperties[p]) ? flow.stepProperties[p] : { propertyName: p, type: 'string' };
41
41
  }
@@ -43,8 +43,8 @@ export async function generateSqsHandler(flow, stepName, suffix, flowOutputDir,
43
43
  });
44
44
  }
45
45
  // Map messageAttributes
46
- if (inTagConfig.messageAttributes && Array.isArray(inTagConfig.messageAttributes)) {
47
- inTagConfig.messageAttributes = inTagConfig.messageAttributes.map(p => {
46
+ if (startConfig.messageAttributes && Array.isArray(startConfig.messageAttributes)) {
47
+ startConfig.messageAttributes = startConfig.messageAttributes.map(p => {
48
48
  if (typeof p === 'string') {
49
49
  return (flow.stepMessageAttributes && flow.stepMessageAttributes[p]) ? flow.stepMessageAttributes[p] : { propertyName: p, type: 'string' };
50
50
  }
@@ -60,7 +60,7 @@ export async function generateSqsHandler(flow, stepName, suffix, flowOutputDir,
60
60
  mainFileName: mainFileName,
61
61
  functionName: functionName,
62
62
  queueName: queueName,
63
- inTagConfig: inTagConfig
63
+ startConfig: startConfig
64
64
  });
65
65
 
66
66
  await fs.writeFile(path.join(flowOutputDir, `${handlerFileName}.js`), handlerContent, 'utf-8');
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { fileURLToPath } from 'url';
6
+ import { generateDsqHandler } from '../BaseDsqHandler.js';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ describe('generateDsqHandler', () => {
12
+ let tmpDir;
13
+ beforeEach(async () => {
14
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'iz-dsq-handler-'));
15
+ });
16
+ afterEach(async () => {
17
+ await fs.rm(tmpDir, { recursive: true, force: true });
18
+ });
19
+
20
+ it('generates a DSQ handler file containing reformatDsqMessage and setValidatorSchema', async () => {
21
+ // Read the template
22
+ const templatePath = path.join(__dirname, '..', '..', 'handlers', 'FlowHandler_HdrDsq.ejs');
23
+ const dsqTemplate = await fs.readFile(templatePath, 'utf-8');
24
+ const templates = { dsq: dsqTemplate };
25
+
26
+ const mockFlow = {
27
+ flowTag: 'createOrder',
28
+ event: ['dsq']
29
+ };
30
+
31
+ await generateDsqHandler(
32
+ mockFlow,
33
+ '', // entry point
34
+ 'HdrDsq', // suffix
35
+ tmpDir, // flowOutputDir
36
+ 'ProcessCreateOrder_Main', // mainFileName
37
+ templates
38
+ );
39
+
40
+ const generatedFilePath = path.join(tmpDir, 'ProcessCreateOrder_HdrDsq.js');
41
+ const generatedContent = await fs.readFile(generatedFilePath, 'utf-8');
42
+
43
+ // Verify it contains setValidatorSchema on line 39 (approx or exactly depending on formatting, let's verify substring)
44
+ expect(generatedContent).toContain('middleware.setValidatorSchema(recordHandlerSharedLib.baseValidatorSchema(');
45
+ expect(generatedContent).toContain('type: "object"');
46
+ expect(generatedContent).toContain('required: [\'body\', \'messageAttributes\']');
47
+
48
+ // Verify it contains reformatDsqMessage
49
+ expect(generatedContent).toContain('record = recordHandlerSharedLib.reformatDsqMessage(record._izContext, record);');
50
+ expect(generatedContent).toContain('record._izContext.logger.debug(\'record ReceiveMsgOutHdrSqs\', record);');
51
+ });
52
+
53
+ it('generates a DSQ handler for step containing step properties mapping', async () => {
54
+ const templatePath = path.join(__dirname, '..', '..', 'handlers', 'FlowHandler_HdrDsq.ejs');
55
+ const dsqTemplate = await fs.readFile(templatePath, 'utf-8');
56
+ const templates = { dsq: dsqTemplate };
57
+
58
+ const mockFlow = {
59
+ flowTag: 'createOrder',
60
+ event: ['dsq'],
61
+ flowSteps: [
62
+ {
63
+ stepName: 'Start',
64
+ properties: ['orderId'],
65
+ messageAttributes: ['userId']
66
+ }
67
+ ],
68
+ stepProperties: {
69
+ orderId: { propertyName: 'orderId', type: 'string', required: true }
70
+ },
71
+ stepMessageAttributes: {
72
+ userId: { propertyName: 'userId', type: 'string', required: true }
73
+ }
74
+ };
75
+
76
+ await generateDsqHandler(
77
+ mockFlow,
78
+ 'StepOne', // step name
79
+ 'HdrDsq', // suffix
80
+ tmpDir, // flowOutputDir
81
+ 'CreateOrderStepOne_Main', // mainFileName
82
+ templates
83
+ );
84
+
85
+ const generatedFilePath = path.join(tmpDir, 'createOrderStepOne_HdrDsq.js');
86
+ const generatedContent = await fs.readFile(generatedFilePath, 'utf-8');
87
+
88
+ // Verify template rendering and function names
89
+ expect(generatedContent).toContain('import createOrderStepOne from \'./CreateOrderStepOne_Main.js\';');
90
+ expect(generatedContent).toContain('createOrderStepOne');
91
+ expect(generatedContent).toContain('"createOrderStepOneHdrDsq"');
92
+ });
93
+ });
@@ -0,0 +1,5 @@
1
+ import { generateDsqHandler } from './BaseDsqHandler.js';
2
+
3
+ export default async function generate(flow, stepName, flowOutputDir, mainFileName, templates) {
4
+ await generateDsqHandler(flow, stepName, 'HdrDsq', flowOutputDir, mainFileName, templates);
5
+ }
@@ -13,14 +13,14 @@ export default async function generate(flow, stepName, flowOutputDir, mainFileNa
13
13
  // Function name matches default export of the referenced _Main.js
14
14
  const functionName = stepName ? `${flowTag}${stepName}` : `Process${upperCase(flowTag)}`;
15
15
 
16
- let inTagConfig = null;
16
+ let startConfig = null;
17
17
  if (!stepName && flow.flowSteps) {
18
- const inTagStep = flow.flowSteps.find(s => s.stepName === 'InTag');
19
- if (inTagStep) {
20
- inTagConfig = { ...inTagStep };
18
+ const startStep = flow.flowSteps.find(s => s.stepName === 'start');
19
+ if (startStep) {
20
+ startConfig = { ...startStep };
21
21
  // Map properties
22
- if (inTagConfig.properties && Array.isArray(inTagConfig.properties)) {
23
- inTagConfig.properties = inTagConfig.properties.map(p => {
22
+ if (startConfig.properties && Array.isArray(startConfig.properties)) {
23
+ startConfig.properties = startConfig.properties.map(p => {
24
24
  if (typeof p === 'string') {
25
25
  return (flow.stepProperties && flow.stepProperties[p]) ? flow.stepProperties[p] : { propertyName: p, type: 'string' };
26
26
  }
@@ -28,8 +28,8 @@ export default async function generate(flow, stepName, flowOutputDir, mainFileNa
28
28
  });
29
29
  }
30
30
  // Map messageAttributes
31
- if (inTagConfig.messageAttributes && Array.isArray(inTagConfig.messageAttributes)) {
32
- inTagConfig.messageAttributes = inTagConfig.messageAttributes.map(p => {
31
+ if (startConfig.messageAttributes && Array.isArray(startConfig.messageAttributes)) {
32
+ startConfig.messageAttributes = startConfig.messageAttributes.map(p => {
33
33
  if (typeof p === 'string') {
34
34
  return (flow.stepMessageAttributes && flow.stepMessageAttributes[p]) ? flow.stepMessageAttributes[p] : { propertyName: p, type: 'string' };
35
35
  }
@@ -45,7 +45,7 @@ export default async function generate(flow, stepName, flowOutputDir, mainFileNa
45
45
  mainFileName: mainFileName,
46
46
  functionName: functionName,
47
47
  queueName: queueName,
48
- inTagConfig: inTagConfig
48
+ startConfig: startConfig
49
49
  });
50
50
 
51
51
  await fs.writeFile(path.join(flowOutputDir, `${handlerFileName}.js`), handlerContent, 'utf-8');
@@ -13,14 +13,14 @@ export default async function generate(flow, stepName, flowOutputDir, mainFileNa
13
13
  // Function name matches default export of the referenced _Main.js
14
14
  const functionName = stepName ? `${flowTag}${stepName}` : `Process${upperCase(flowTag)}`;
15
15
 
16
- let inTagConfig = null;
16
+ let startConfig = null;
17
17
  if (!stepName && flow.flowSteps) {
18
- const inTagStep = flow.flowSteps.find(s => s.stepName === 'InTag');
19
- if (inTagStep) {
20
- inTagConfig = { ...inTagStep };
18
+ const startStep = flow.flowSteps.find(s => s.stepName === 'start');
19
+ if (startStep) {
20
+ startConfig = { ...startStep };
21
21
  // Map properties
22
- if (inTagConfig.properties && Array.isArray(inTagConfig.properties)) {
23
- inTagConfig.properties = inTagConfig.properties.map(p => {
22
+ if (startConfig.properties && Array.isArray(startConfig.properties)) {
23
+ startConfig.properties = startConfig.properties.map(p => {
24
24
  if (typeof p === 'string') {
25
25
  return (flow.stepProperties && flow.stepProperties[p]) ? flow.stepProperties[p] : { propertyName: p, type: 'string' };
26
26
  }
@@ -28,8 +28,8 @@ export default async function generate(flow, stepName, flowOutputDir, mainFileNa
28
28
  });
29
29
  }
30
30
  // Map messageAttributes
31
- if (inTagConfig.messageAttributes && Array.isArray(inTagConfig.messageAttributes)) {
32
- inTagConfig.messageAttributes = inTagConfig.messageAttributes.map(p => {
31
+ if (startConfig.messageAttributes && Array.isArray(startConfig.messageAttributes)) {
32
+ startConfig.messageAttributes = startConfig.messageAttributes.map(p => {
33
33
  if (typeof p === 'string') {
34
34
  return (flow.stepMessageAttributes && flow.stepMessageAttributes[p]) ? flow.stepMessageAttributes[p] : { propertyName: p, type: 'string' };
35
35
  }
@@ -45,7 +45,7 @@ export default async function generate(flow, stepName, flowOutputDir, mainFileNa
45
45
  mainFileName: mainFileName,
46
46
  functionName: functionName,
47
47
  queueName: queueName,
48
- inTagConfig: inTagConfig
48
+ startConfig: startConfig
49
49
  });
50
50
 
51
51
  await fs.writeFile(path.join(flowOutputDir, `${handlerFileName}.js`), handlerContent, 'utf-8');
@@ -4,6 +4,7 @@ import lambdaSyncInv from './lambdaSyncInv.js';
4
4
  import lambdaSyncApi from './lambdaSyncApi.js';
5
5
  import eventBridge from './eventBridge.js';
6
6
  import s3 from './s3.js';
7
+ import dsq from './dsq.js';
7
8
 
8
9
  export const EVENT_HANDLERS = {
9
10
  ownTopic,
@@ -11,5 +12,6 @@ export const EVENT_HANDLERS = {
11
12
  lambdaSyncInv,
12
13
  lambdaSyncApi,
13
14
  eventBridge,
14
- s3
15
+ s3,
16
+ dsq
15
17
  };
@@ -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
  };
@@ -0,0 +1,129 @@
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 => { -%>
31
+ '<%- prop.propertyName %>': { type: '<%- prop.type || 'string' %>' },
32
+ <% }) -%>
33
+ },
34
+ required: [ <%- startConfig.properties.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
35
+ <% } -%>
36
+ //(</validatorSchema>)
37
+ };
38
+
39
+ <% if (typeof startConfig !== 'undefined' && startConfig !== null && startConfig.messageAttributes && startConfig.messageAttributes.length > 0) { -%>
40
+ let messageAttributeValidatorSchema = {
41
+ type: 'object',
42
+ properties: {
43
+ <% startConfig.messageAttributes.forEach(prop => { -%>
44
+ '<%- prop.propertyName %>': {
45
+ type: 'object',
46
+ properties: {
47
+ Type: { type: 'string' },
48
+ Value: { type: '<%- prop.type || 'string' %>' }
49
+ },
50
+ required: ['Type', 'Value']
51
+ },
52
+ <% }) -%>
53
+ },
54
+ required: [ <%- startConfig.messageAttributes.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
55
+ };
56
+ <% } else { -%>
57
+ let messageAttributeValidatorSchema = null;
58
+ <% } -%>
59
+
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'
70
+ }
71
+ }
72
+ }));
73
+
74
+ export const main = middleware.wrap(async (event, context) => {
75
+
76
+ try {
77
+
78
+ let recordPromises = [];
79
+
80
+ // 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
+ }))
112
+
113
+ Logger.debug('before Promise.all(recordPromises) in handler');
114
+ try {
115
+ // --- main await all promises
116
+ await Promise.all(recordPromises); // await all promises
117
+
118
+ return event.Records // return all for local testing
119
+
120
+ } catch {
121
+ Logger.debug('Promise.all(recordPromises) in handler threw error (at least one record did no resolve)');
122
+ }
123
+ Logger.debug('after Promise.all(recordPromises) in handler');
124
+
125
+ } catch (err) {
126
+ Logger.error('Unhandled Error, LambdaFunctionHdrSqs: ', err);
127
+ throw (err);
128
+ }
129
+ });
@@ -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
  };
@@ -24,27 +24,34 @@ 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' %>' },
30
+ <% startConfig.properties.forEach(prop => { -%>
31
+ '<%- prop.propertyName %>': { type: '<%- prop.type || 'string' %>' },
32
32
  <% }) -%>
33
33
  },
34
- required: [ <%- inTagConfig.properties.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
34
+ required: [ <%- startConfig.properties.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
35
35
  <% } -%>
36
36
  //(</validatorSchema>)
37
37
  };
38
38
 
39
- <% if (typeof inTagConfig !== 'undefined' && inTagConfig !== null && inTagConfig.messageAttributes && inTagConfig.messageAttributes.length > 0) { -%>
39
+ <% if (typeof startConfig !== 'undefined' && startConfig !== null && startConfig.messageAttributes && startConfig.messageAttributes.length > 0) { -%>
40
40
  let messageAttributeValidatorSchema = {
41
41
  type: 'object',
42
42
  properties: {
43
- <% inTagConfig.messageAttributes.forEach(prop => { -%>
44
- <%- prop.propertyName %>: { type: '<%- prop.type || 'string' %>' },
43
+ <% startConfig.messageAttributes.forEach(prop => { -%>
44
+ '<%- prop.propertyName %>': {
45
+ type: 'object',
46
+ properties: {
47
+ Type: { type: 'string' },
48
+ Value: { type: '<%- prop.type || 'string' %>' }
49
+ },
50
+ required: ['Type', 'Value']
51
+ },
45
52
  <% }) -%>
46
53
  },
47
- required: [ <%- inTagConfig.messageAttributes.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
54
+ required: [ <%- startConfig.messageAttributes.filter(p => p.required !== false).map(p => `'${p.propertyName}'`).join(', ') %> ]
48
55
  };
49
56
  <% } else { -%>
50
57
  let messageAttributeValidatorSchema = null;
@@ -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
  }
@@ -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'),
@@ -75,22 +75,22 @@ 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'
78
+ const startStep = flow.flowSteps && flow.flowSteps.find(
79
+ s => s.stepName === 'start'
80
80
  );
81
- let inTagConfig = inTagStep ? { ...inTagStep } : null;
81
+ let startConfig = startStep ? { ...startStep } : null;
82
82
 
83
- if (inTagConfig) {
84
- if (inTagConfig.properties && Array.isArray(inTagConfig.properties)) {
85
- inTagConfig.properties = inTagConfig.properties.map(p => {
83
+ if (startConfig) {
84
+ if (startConfig.properties && Array.isArray(startConfig.properties)) {
85
+ startConfig.properties = startConfig.properties.map(p => {
86
86
  if (typeof p === 'string') {
87
87
  return (flow.stepProperties && flow.stepProperties[p]) ? flow.stepProperties[p] : { propertyName: p, type: 'string' };
88
88
  }
89
89
  return p;
90
90
  });
91
91
  }
92
- if (inTagConfig.messageAttributes && Array.isArray(inTagConfig.messageAttributes)) {
93
- inTagConfig.messageAttributes = inTagConfig.messageAttributes.map(p => {
92
+ if (startConfig.messageAttributes && Array.isArray(startConfig.messageAttributes)) {
93
+ startConfig.messageAttributes = startConfig.messageAttributes.map(p => {
94
94
  if (typeof p === 'string') {
95
95
  return (flow.stepMessageAttributes && flow.stepMessageAttributes[p]) ? flow.stepMessageAttributes[p] : { propertyName: p, type: 'string' };
96
96
  }
@@ -118,7 +118,7 @@ export async function generateFlowMainFunction(allSchemas, options) {
118
118
  topicArn: topicArn,
119
119
  generateCode: generateCode,
120
120
  flow: flow,
121
- inTagConfig: inTagConfig,
121
+ startConfig: startConfig,
122
122
  tableName: tableName
123
123
  });
124
124
  await fs.writeFile(
@@ -138,7 +138,7 @@ export async function generateFlowMainFunction(allSchemas, options) {
138
138
  upperCaseFlowTag: upperCaseFlowTag,
139
139
  generateCode: generateCode,
140
140
  flow: flow,
141
- inTagConfig: inTagConfig,
141
+ startConfig: startConfig,
142
142
  tableName: tableName
143
143
  });
144
144
  await fs.writeFile(
@@ -162,7 +162,7 @@ export async function generateFlowMainFunction(allSchemas, options) {
162
162
  upperCaseFlowTag: upperCaseFlowTag,
163
163
  generateCode: generateCode,
164
164
  flow: flow,
165
- inTagConfig: inTagConfig
165
+ startConfig: startConfig
166
166
  });
167
167
 
168
168
  await fs.writeFile(
@@ -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 => {
@@ -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
 
@@ -53,7 +54,7 @@ export async function generateFlowSteps(allSchemas, options) {
53
54
 
54
55
  for (const stepConfig of normalizeFlowSteps(flow.flowSteps)) {
55
56
  const stepName = stepConfig.stepName;
56
- if (['In', 'InTag', 'Out', 'beforeLogical'].includes(stepName)) continue;
57
+ if (['In', 'start', 'Out', 'beforeLogical'].includes(stepName)) continue;
57
58
 
58
59
  if (!createdDir) {
59
60
  await fs.mkdir(flowOutputDir, { recursive: true });
@@ -179,7 +180,7 @@ export async function generateFlowSteps(allSchemas, options) {
179
180
  }
180
181
 
181
182
  // 2. Generate Handlers based on stepConfig.event
182
- const events = normalizeEvents(stepConfig.event, ['sqs']); // Default to sqs if missing
183
+ const events = normalizeEvents(stepConfig.event, ['extTopic']); // Default to extTopic if missing
183
184
  for (const ev of events) {
184
185
  if (ev === 'sns') continue;
185
186
  const handlerConfig = getStepEventHandlerConfig(ev);
@@ -21,8 +21,54 @@ 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
+ const hasStart = flow.flowSteps.some(s => s.stepName?.toLowerCase() === 'start');
44
+
45
+ if (flow.event) {
46
+ if (!hasStart) {
47
+ flow.flowSteps.unshift({
48
+ stepName: 'Start',
49
+ properties: flow.properties || [],
50
+ messageAttributes: flow.messageAttributes || []
51
+ });
52
+ }
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
+ });
69
+ }
70
+ }
71
+ }
72
+ return flow;
73
+ });
74
+ }
@@ -1,6 +1,5 @@
1
- const ALLOWED_MAIN_EVENTS = ['ownTopic', 'extTopic', 'lambdaSyncInv', 'lambdaSyncApi', 'eventBridge', 's3', 'dsq'];
1
+ const ALLOWED_EVENTS = ['ownTopic', 'extTopic', 'lambdaSyncInv', 'lambdaSyncApi', 'eventBridge', 's3', 'dsq'];
2
2
  const MAIN_ASYNC_EVENTS = ['ownTopic', 'extTopic', 's3'];
3
- const ALLOWED_STEP_EVENTS = ['sqs', 'ownTopic', 'extTopic', 'dsq', 'lambdaSyncInv', 'lambdaSyncApi'];
4
3
  const RESERVED_SYSTEM_FLOW_TAGS = new Set([
5
4
  'Create',
6
5
  'Update',
@@ -73,9 +72,9 @@ export function validateFlowsForGeneration(flows) {
73
72
  }
74
73
 
75
74
  const events = asArray(flow.event);
76
- const invalidMainEvents = events.filter(event => !ALLOWED_MAIN_EVENTS.includes(event));
75
+ const invalidMainEvents = events.filter(event => !ALLOWED_EVENTS.includes(event));
77
76
  if (invalidMainEvents.length > 0) {
78
- errors.push(`Flow '${flowTag}' uses unsupported main events: ${invalidMainEvents.join(', ')}. Allowed are: ${ALLOWED_MAIN_EVENTS.join(', ')}.`);
77
+ errors.push(`Flow '${flowTag}' uses unsupported main events: ${invalidMainEvents.join(', ')}. Allowed are: ${ALLOWED_EVENTS.join(', ')}.`);
79
78
  }
80
79
 
81
80
  const mainAsyncEvents = events.filter(event => MAIN_ASYNC_EVENTS.includes(event));
@@ -86,7 +85,7 @@ export function validateFlowsForGeneration(flows) {
86
85
  const flowSteps = Array.isArray(flow.flowSteps) ? flow.flowSteps : [];
87
86
  const seenStepNames = new Set();
88
87
  let anyStepHasPlugInHooks = false;
89
- let inTagCount = 0;
88
+ let startStepCount = 0;
90
89
 
91
90
  for (const step of flowSteps) {
92
91
  const stepName = step?.stepName;
@@ -101,10 +100,10 @@ export function validateFlowsForGeneration(flows) {
101
100
  seenStepNames.add(stepName);
102
101
 
103
102
  if (stepName === 'In') {
104
- errors.push(`Flow '${flowTag}' uses legacy stepName 'In'. Use 'InTag'.`);
103
+ errors.push(`Flow '${flowTag}' uses legacy stepName 'In'. Use 'start'.`);
105
104
  }
106
- if (stepName === 'InTag') {
107
- inTagCount += 1;
105
+ if (stepName === 'start') {
106
+ startStepCount += 1;
108
107
  }
109
108
 
110
109
  if (step.plugInHooks && step.plugInHooks.length > 0) {
@@ -112,22 +111,21 @@ export function validateFlowsForGeneration(flows) {
112
111
  }
113
112
 
114
113
  const stepEvents = asArray(step.event);
115
- const invalidStepEvents = stepEvents.filter(event => !ALLOWED_STEP_EVENTS.includes(event));
114
+ const invalidStepEvents = stepEvents.filter(event => !ALLOWED_EVENTS.includes(event));
116
115
  if (invalidStepEvents.length > 0) {
117
- errors.push(`Flow '${flowTag}' step '${stepName}' uses unsupported step events: ${invalidStepEvents.join(', ')}. Allowed are: ${ALLOWED_STEP_EVENTS.join(', ')}.`);
116
+ errors.push(`Flow '${flowTag}' step '${stepName}' uses unsupported step events: ${invalidStepEvents.join(', ')}. Allowed are: ${ALLOWED_EVENTS.join(', ')}.`);
118
117
  }
119
118
 
120
119
  validateNamedRefs(flowTag, stepName, step.properties, flow.stepProperties, 'properties', errors);
121
120
  validateNamedRefs(flowTag, stepName, step.messageAttributes, flow.stepMessageAttributes, 'messageAttributes', errors);
122
121
  }
123
122
 
124
- if (inTagCount > 1) {
125
- errors.push(`Flow '${flowTag}' has multiple 'InTag' steps. Only one is allowed.`);
123
+ if (startStepCount > 1) {
124
+ errors.push(`Flow '${flowTag}' has multiple 'start' steps. Only one is allowed.`);
126
125
  }
127
126
 
128
- if (anyStepHasPlugInHooks && inTagCount === 0) {
129
- errors.push(`Flow '${flowTag}' uses plugInHooks but does not have an 'InTag' step. If flow needs plugin-aware main lambda, it must include an 'InTag' step.`);
130
- }
127
+ // ponytail: relaxed anyStepHasPlugInHooks && startStepCount === 0 requirement
128
+ // since unified entry points (step 0) can now handle plugins directly without start.
131
129
 
132
130
  if (anyStepHasPlugInHooks) {
133
131
  const forbiddenEvents = ['lambdaSyncInv', 'lambdaSyncApi', 's3', 'eventBridge'];
@@ -316,30 +316,30 @@ const TOPIC_NAME_GRAPH_HANDLER = {
316
316
  };
317
317
 
318
318
  const TOPIC_NAME_GENERATE_CODE = {
319
- updateRelComplete: 'UpdateRelationshipComplete',
320
- createRelComplete: 'CreateRelationshipComplete',
321
- deleteRelComplete: 'DeleteRelationshipComplete',
322
- getRelComplete: 'GetRelationshipComplete',
319
+ updateRelComplete: 'UpdateRelationshipCompleteHdrSqs',
320
+ createRelComplete: 'CreateRelationshipCompleteHdrSqs',
321
+ deleteRelComplete: 'DeleteRelationshipCompleteHdrSqs',
322
+ getRelComplete: 'GetRelationshipCompleteHdrSqs',
323
323
  outUpdateRelComplete: 'UpdateRelationship_Out',
324
324
  outCreateRelComplete: 'CreateRelationship_Out',
325
325
  outDeleteRelComplete: 'DeleteRelationship_Out',
326
326
  outGetRelComplete: 'GetRelationship_Out',
327
- updateNodeComplete: 'UpdateComplete',
327
+ updateNodeComplete: 'UpdateCompleteHdrSqs',
328
328
  outUpdateNodeComplete: 'Update_Out',
329
- createNodeComplete: 'CreateComplete',
329
+ createNodeComplete: 'CreateCompleteHdrSqs',
330
330
  outCreateNodeComplete: 'Create_Out',
331
- deleteNodeComplete: 'DeleteComplete',
331
+ deleteNodeComplete: 'DeleteCompleteHdrSqs',
332
332
  outDeleteNodeComplete: 'Delete_Out',
333
- getNodeComplete: 'GetComplete',
333
+ getNodeComplete: 'GetCompleteHdrSqs',
334
334
  outGetNodeComplete: 'Get_Out',
335
335
  reservedLimitComplete: 'ReservedLimitComplete',
336
336
  createPreSignUrl: 'CreatePreSignUrl',
337
337
  flowSchemaOwnTopicComplete: 'FlowSchemaOwnTopicComplete',
338
338
  flowSchemaOwnTopicEndpointComplete: 'FlowSchemaOwnTopicEndpointComplete',
339
339
  createRecordComplete: 'CreateRecordComplete',
340
- changeRelationshipComplete: 'ChangeRelationshipComplete',
340
+ changeRelationshipComplete: 'ChangeRelationshipCompleteHdrSqs',
341
341
  outChangeRelationshipComplete: 'ChangeRelationship_Out',
342
- moveRelationshipComplete: 'MoveRelationshipComplete',
342
+ moveRelationshipComplete: 'MoveRelationshipCompleteHdrSqs',
343
343
  outMoveRelationshipComplete: 'MoveRelationship_Out'
344
344
  };
345
345
 
@@ -79,9 +79,9 @@ export async function generateFlowResourceYaml(allSchemas, options) {
79
79
  if (Array.isArray(flow.flowSteps)) {
80
80
  for (const stepConfig of flow.flowSteps) {
81
81
  const stepName = stepConfig.stepName;
82
- if (stepName === 'InTag') continue;
82
+ if (stepName === 'start') continue;
83
83
 
84
- const events = stepConfig.event || ['sqs'];
84
+ const events = stepConfig.event || ['extTopic'];
85
85
  for (const ev of events) {
86
86
  let queueName = `${flowTag}${stepName}`;
87
87
  if (stepName === 'Complete') {
@@ -8,6 +8,7 @@ import { parseFlowSchemas } from './parsers/flowSchemaParser.js';
8
8
  import { generateFlowEntryPoint } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowEntryPointBase.js';
9
9
  import { generateFlowMainFunction } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowMainFunctionBase.js';
10
10
  import { generateFlowSteps } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepBase.js';
11
+ import { normalizeUnifiedFlows } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepNormalizer.js';
11
12
 
12
13
  import { generateEndpointsFlow } from './codeGenerators/app/src/generatedCode/Flow/FlowObjects/EndpointsGenerator.js';
13
14
  import { generateFlowOut } from './codeGenerators/resource/sls_yaml/FlowOutGenerator.js';
@@ -189,25 +190,19 @@ export async function generateCode(rootPath, options = {}) {
189
190
  }
190
191
  }
191
192
 
193
+ let cleanupStarted = false;
194
+ let generationSucceeded = false;
195
+ const backedUpDirs = new Set();
196
+ const dirsToBackup = [
197
+ generatedCodePath,
198
+ path.join(outputPath, 'app', 'sls_yaml', 'generatedCode'),
199
+ path.join(outputPath, 'resource', 'sls_yaml', 'generatedCode')
200
+ ];
201
+
192
202
  // ponytail: ensure hooks are restored even if generation fails
193
203
  try {
194
- console.log(
195
- '[INFO] [generateCode] STATUS=CLEANUP | Removing old generatedCode directories...'
196
- );
197
- await fs.promises.rm(generatedCodePath, {
198
- recursive: true,
199
- force: true
200
- });
201
- await fs.promises.rm(
202
- path.join(outputPath, 'app', 'sls_yaml', 'generatedCode'),
203
- { recursive: true, force: true }
204
- );
205
- await fs.promises.rm(
206
- path.join(outputPath, 'resource', 'sls_yaml', 'generatedCode'),
207
- { recursive: true, force: true }
208
- );
204
+ policyRegistry.clear();
209
205
 
210
- policyRegistry.clear();
211
206
 
212
207
  // 1. Parsing & Hydration Pipeline
213
208
  console.log('[INFO] [generateCode] STATUS=PARSING | ObjectSchemas...');
@@ -227,9 +222,34 @@ export async function generateCode(rootPath, options = {}) {
227
222
  flowsForGeneration: flowData.flows || []
228
223
  };
229
224
 
225
+ console.log('[INFO] [generateCode] STATUS=NORMALIZING | Adapting Unified Flows...');
226
+ normalizeUnifiedFlows(allSchemas.flowsForGeneration);
227
+
230
228
  console.log('[INFO] [generateCode] STATUS=VALIDATING | Validating Flows...');
231
229
  validateFlowsForGeneration(allSchemas.flowsForGeneration);
232
230
 
231
+ console.log(
232
+ '[INFO] [generateCode] STATUS=BACKUP_DIRS | Backing up old generatedCode directories...'
233
+ );
234
+ for (const dir of dirsToBackup) {
235
+ if (fs.existsSync(dir)) {
236
+ if (fs.existsSync(`${dir}.bak`)) {
237
+ await fs.promises.rm(`${dir}.bak`, { recursive: true, force: true });
238
+ }
239
+ await fs.promises.rename(dir, `${dir}.bak`);
240
+ backedUpDirs.add(dir);
241
+ }
242
+ }
243
+
244
+ cleanupStarted = true;
245
+
246
+ console.log(
247
+ '[INFO] [generateCode] STATUS=CLEANUP | Removing old generatedCode directories before generating new files...'
248
+ );
249
+ for (const dir of dirsToBackup) {
250
+ await fs.promises.rm(dir, { recursive: true, force: true });
251
+ }
252
+
233
253
  // 2. Generation Pipeline
234
254
  await generateEndpointsFlow(allSchemas, generatorOptions);
235
255
  await generateFlowMainFunction(allSchemas, generatorOptions);
@@ -263,8 +283,28 @@ export async function generateCode(rootPath, options = {}) {
263
283
  await generateSharedResourceYaml(allSchemas, generatorOptions);
264
284
  await generateInitialSetup(allSchemas, generatorOptions);
265
285
 
286
+ generationSucceeded = true;
266
287
 
267
288
  } finally {
289
+ if (generationSucceeded) {
290
+ console.log('[INFO] [generateCode] STATUS=CLEANUP_BAK | Generation successful, removing .bak directories...');
291
+ for (const dir of dirsToBackup) {
292
+ if (fs.existsSync(`${dir}.bak`)) {
293
+ await fs.promises.rm(`${dir}.bak`, { recursive: true, force: true });
294
+ }
295
+ }
296
+ } else if (cleanupStarted) {
297
+ console.log('[INFO] [generateCode] STATUS=FAILED | Generation failed, rolling back directories...');
298
+ for (const dir of dirsToBackup) {
299
+ if (fs.existsSync(dir)) {
300
+ await fs.promises.rm(dir, { recursive: true, force: true });
301
+ }
302
+ if (backedUpDirs.has(dir) && fs.existsSync(`${dir}.bak`)) {
303
+ await fs.promises.rename(`${dir}.bak`, dir);
304
+ }
305
+ }
306
+ }
307
+
268
308
  // Restore backed-up hooks
269
309
  if (hookDirs.size > 0 || hookBackups.length > 0) {
270
310
  console.log(
@@ -279,14 +319,19 @@ export async function generateCode(rootPath, options = {}) {
279
319
  }
280
320
  }
281
321
 
282
- console.log(
283
- '[INFO] [generateCode] STATUS=HOOKS | Processing hook folders beside source folders...'
284
- );
285
- processSourceHooks(generatorOptions.outputPath);
322
+ if (generationSucceeded) {
323
+ console.log(
324
+ '[INFO] [generateCode] STATUS=HOOKS | Processing hook folders beside source folders...'
325
+ );
326
+ processSourceHooks(generatorOptions.outputPath);
286
327
 
287
- console.log(
288
- '[INFO] [generateCode] STATUS=FINISHED | Code generation complete.'
289
- );
328
+ console.log(
329
+ '[INFO] [generateCode] STATUS=FINISHED | Code generation complete.'
330
+ );
331
+ } else {
332
+ console.log(
333
+ '[INFO] [generateCode] STATUS=FAILED | Code generation failed. Hook files were restored, but processSourceHooks was skipped.'
334
+ );
335
+ }
290
336
  }
291
337
  }
292
-
@@ -17,14 +17,14 @@ export default {
17
17
  },
18
18
  flowSteps: [
19
19
  {
20
- stepName: 'InTag',
20
+ stepName: 'start',
21
21
  properties: [],
22
22
  messageAttributes: ['userId', 'correlationId']
23
23
  },
24
24
  <% if (hasBeforeLogicalStep) { %>
25
25
  {
26
26
  stepName: 'BeforeLogical',
27
- event: ['sqs'],
27
+ event: ['extTopic'],
28
28
  properties: [],
29
29
  messageAttributes: ['userId', 'correlationId']
30
30
  },
@@ -32,7 +32,7 @@ export default {
32
32
  <% if (['Create', 'Update', 'Delete'].includes(action)) { %>
33
33
  {
34
34
  stepName: 'Complete',
35
- event: ['sqs'],
35
+ event: ['extTopic'],
36
36
  properties: [],
37
37
  messageAttributes: ['userId', 'correlationId']
38
38
  }
@@ -12,7 +12,7 @@ export default {
12
12
  outputTopic: true,
13
13
  flowSteps: [
14
14
  {
15
- stepName: 'InTag',
15
+ stepName: 'start',
16
16
  properties: [],
17
17
  messageAttributes: []
18
18
  }
@@ -17,14 +17,14 @@ export default {
17
17
  },
18
18
  flowSteps: [
19
19
  {
20
- stepName: 'InTag',
20
+ stepName: 'start',
21
21
  properties: [],
22
22
  messageAttributes: ['userId', 'correlationId']
23
23
  }
24
24
  <% if (hasCompleteStep) { %>,
25
25
  {
26
26
  stepName: 'Complete',
27
- event: ['sqs'],
27
+ event: ['extTopic'],
28
28
  properties: [],
29
29
  messageAttributes: ['userId', 'correlationId']
30
30
  }
@@ -15,7 +15,7 @@ export default {
15
15
  },
16
16
  steps: [
17
17
  {
18
- stepName: 'InTag',
18
+ stepName: 'start',
19
19
  messageAttributes: ['userId', 'correlationId']
20
20
  }
21
21
  ]
@@ -0,0 +1,7 @@
1
+ import { generateCode } from './src/generateCode.js';
2
+
3
+ async function run() {
4
+ const rootPath = '/home/azure/Working/test/test-lib-generateCode/test-cases/real/case01-UnitType/workspace';
5
+ await generateCode(rootPath);
6
+ }
7
+ run().catch(console.error);
@@ -0,0 +1,17 @@
1
+ import { normalizeUnifiedFlows, normalizeFlowSteps } from './src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepNormalizer.js';
2
+
3
+ const rawFlows = [
4
+ {
5
+ event: 'api',
6
+ flowSteps: {
7
+ MyStep: { properties: [] }
8
+ }
9
+ }
10
+ ];
11
+
12
+ try {
13
+ const result = normalizeUnifiedFlows(rawFlows);
14
+ console.log(JSON.stringify(result, null, 2));
15
+ } catch (e) {
16
+ console.error(e.message);
17
+ }