@izara_project/izara-core-generate-service-code 1.0.54 → 1.0.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izara_project/izara-core-generate-service-code",
3
- "version": "1.0.54",
3
+ "version": "1.0.55",
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": {
@@ -103,7 +103,7 @@ export async function generateFunctionYaml(allSchemas, options) {
103
103
 
104
104
  // Function Name Config
105
105
  // e.g. ProcessCreateSqs or CreateCompleteSqs or GetApi
106
- let suffixType = handlerSuffix.replace('Hdr', ''); // Sqs, Api, Inv
106
+ let suffixType = handlerSuffix.startsWith('Hdr') ? handlerSuffix : handlerSuffix.replace('Hdr', '');
107
107
  let functionNameConfig = `${handlerBaseName}${suffixType}`;
108
108
  if (isCrud && handlerBaseName === `Process${upperCase(flowTag)}`) {
109
109
  // Legacy matches: CreateSqs, GetApi instead of ProcessCreateSqs
@@ -133,19 +133,19 @@ export async function generateFunctionYaml(allSchemas, options) {
133
133
 
134
134
  if (isCrud || isRel) {
135
135
  queueName = handlerBaseName.replace('Process', '');
136
- if (handlerSuffix === 'HdrSqs' || handlerSuffix === 'HdrDsq') {
137
- queueName += handlerSuffix.replace('Hdr', '');
138
- }
136
+ if (handlerSuffix === 'HdrSqs' || handlerSuffix === 'HdrDsq') {
137
+ queueName += handlerSuffix;
138
+ }
139
139
  } else {
140
140
  // For Custom Flows (e.g. OwnTopicFlowProcess, ProcessExtTopicFlow)
141
141
  // The queue name exactly matches the handler name without the underscore.
142
142
  if (handlerSuffix === 'HdrSqs' || handlerSuffix === 'HdrDsq') {
143
143
  if (handlerBaseName === `Process${upperCase(flowTag)}` && flowDefForQueue && flowDefForQueue.event && flowDefForQueue.event.includes('ownTopic')) {
144
144
  // ownTopic flows in FlowResourceYamlGenerator omit 'Process' for the queue
145
- queueName = flowTag + handlerSuffix.replace('Hdr', '');
146
- } else {
147
- queueName = handlerBaseName + handlerSuffix.replace('Hdr', '');
148
- }
145
+ queueName = flowTag + handlerSuffix;
146
+ } else {
147
+ queueName = handlerBaseName + handlerSuffix;
148
+ }
149
149
  } else {
150
150
  queueName = handlerBaseName;
151
151
  }
@@ -153,7 +153,7 @@ export async function generateFunctionYaml(allSchemas, options) {
153
153
 
154
154
  // Api routes
155
155
  let apiRoutes = [];
156
- if (suffixType === 'Api') {
156
+ if (suffixType === 'HdrApi' || suffixType === 'Api') {
157
157
  const routesList = isCrud ? crudApiRoutes : (isRel ? relApiRoutes : []);
158
158
  const actionPath = flowTag.toLowerCase().replace('relationship', '');
159
159
  apiRoutes = routesList.map(r => ({
@@ -166,7 +166,7 @@ export async function generateFunctionYaml(allSchemas, options) {
166
166
  const handlerPath = `src/generatedCode/${subDir}/${flowTag}/source/${fileNameWithoutExt}.main`;
167
167
 
168
168
  let filterPatterns = '';
169
- if (handlerBaseName.endsWith('Complete') && (suffixType === 'Sqs' || suffixType === 'Dsq')) {
169
+ if (handlerBaseName.endsWith('Complete') && (suffixType === 'HdrSqs' || suffixType === 'HdrDsq' || suffixType === 'Sqs' || suffixType === 'Dsq')) {
170
170
  filterPatterns = ` filterPatterns: #**** need to update serverless framework upper v.2.69.1
171
171
  - body: {"MessageAttributes":{"callingFlow":{"Value":["\${self:custom.iz_resourcePrefix}${functionNameConfig}"]}}} # functionName of callingFlow
172
172
  - body: {"MessageAttributes":{"callingFlow":{"Value":[{"exists":false}]}}}`;
@@ -190,15 +190,15 @@ export async function generateFunctionYaml(allSchemas, options) {
190
190
  };
191
191
 
192
192
  let yamlBlock = '';
193
- if (schedules.length > 0 && suffixType === 'Inv') {
193
+ if (schedules.length > 0 && (suffixType === 'HdrInv' || suffixType === 'Inv')) {
194
194
  yamlBlock = ejs.render(templates.eventBridge, renderVars);
195
- } else if (suffixType === 'Sqs' || suffixType === 'Dsq') {
195
+ } else if (suffixType === 'HdrSqs' || suffixType === 'HdrDsq' || suffixType === 'Sqs' || suffixType === 'Dsq') {
196
196
  yamlBlock = ejs.render(templates.sqs, renderVars);
197
- } else if (suffixType === 'Inv') {
197
+ } else if (suffixType === 'HdrInv' || suffixType === 'Inv') {
198
198
  yamlBlock = ejs.render(templates.inv, renderVars);
199
- } else if (suffixType === 'Api') {
199
+ } else if (suffixType === 'HdrApi' || suffixType === 'Api') {
200
200
  yamlBlock = ejs.render(templates.api, renderVars);
201
- } else if (suffixType === 'Wbs') {
201
+ } else if (suffixType === 'HdrWbs' || suffixType === 'Wbs') {
202
202
  renderVars.routeName = flowTag;
203
203
  yamlBlock = ejs.render(templates.wbs, renderVars);
204
204
  } else {
@@ -17,14 +17,7 @@ function lowerCase(str) {
17
17
  }
18
18
 
19
19
  function shortNameHandler(handlerType) {
20
- // Basic mapping for short names
21
- const map = {
22
- 'HdrSqs': 'Sqs',
23
- 'HdrApi': 'Api',
24
- 'HdrWbs': 'Wbs',
25
- 'HdrDsq': 'Dsq'
26
- };
27
- return map[handlerType] || handlerType;
20
+ return handlerType;
28
21
  }
29
22
 
30
23
  export async function generateStatusField(flowSchema, appPath, outputBaseDir) {
@@ -12,13 +12,7 @@ function upperCase(str) {
12
12
  }
13
13
 
14
14
  function shortNameHandler(handlerType) {
15
- const map = {
16
- 'HdrSqs': 'Sqs',
17
- 'HdrApi': 'Api',
18
- 'HdrWbs': 'Wbs',
19
- 'HdrDsq': 'Dsq'
20
- };
21
- return map[handlerType] || handlerType;
15
+ return handlerType;
22
16
  }
23
17
 
24
18
  export async function generateWebSocket(allLocalFlowSchemas, appPath, outputBaseDir) {
@@ -14,9 +14,9 @@ export async function generateSqsHandler(flow, stepName, suffix, flowOutputDir,
14
14
  // For steps: ${flowTag}${flowStepName} (e.g. OwnTopicFlowProcess)
15
15
  const functionName = stepName ? `${flowTag}${stepName}` : `Process${upperCase(flowTag)}`;
16
16
 
17
- // Queue name: match the ARN generated in FunctionYamlGenerator
18
- // suffix e.g. HdrSqs -> Sqs
19
- const queueNameSuffix = suffix.replace('Hdr', '');
17
+ // Queue name: keep the full handler suffix so handler code matches
18
+ // the generated queue/resource names.
19
+ const queueNameSuffix = suffix;
20
20
  let queueName = '';
21
21
  if (stepName) {
22
22
  queueName = `${flowTag}${stepName}${queueNameSuffix}`;
@@ -13,13 +13,7 @@ function upperCase(str) {
13
13
  }
14
14
 
15
15
  function shortNameHandler(handlerType) {
16
- const map = {
17
- 'HdrSqs': 'Sqs',
18
- 'HdrApi': 'Api',
19
- 'HdrWbs': 'Wbs',
20
- 'HdrDsq': 'Dsq'
21
- };
22
- return map[handlerType] || handlerType;
16
+ return handlerType;
23
17
  }
24
18
 
25
19
  export async function generateTriggerCache(flowSchema, appPath, outputBaseDir) {
@@ -14,13 +14,7 @@ function upperCase(str) {
14
14
  }
15
15
 
16
16
  function shortNameHandler(handlerType) {
17
- const map = {
18
- HdrSqs: 'Sqs',
19
- HdrApi: 'Api',
20
- HdrWbs: 'Wbs',
21
- HdrDsq: 'Dsq'
22
- };
23
- return map[handlerType] || handlerType;
17
+ return handlerType;
24
18
  }
25
19
 
26
20
  export async function generateRegisterFlow(allLocalFlowSchemas, _appPath, outputBaseDir) {
@@ -72,15 +72,15 @@ export async function generateDynamoDBTables(allSchemas, options) {
72
72
  }
73
73
  }
74
74
 
75
- let generatedCount = 0;
76
- const yamlOutputPath = path.join(resourceYamlDir, 'generated-dynamoDB-table.yml');
75
+ let generatedCount = 0;
76
+ const yamlOutputPath = path.join(resourceYamlDir, 'generated-dynamoDB-table.yml');
77
77
 
78
- // Generate system tables first
79
- const systemYamlTplPath = path.join(__dirname, 'templates', 'SystemDynamoDB_Yaml.ejs');
80
- const systemYamlTpl = await fs.readFile(systemYamlTplPath, 'utf-8');
81
- const systemYamlContent = ejs.render(systemYamlTpl, {});
82
- await appendToFile(yamlOutputPath, systemYamlContent);
83
- generatedCount += 8; // 8 system tables
78
+ // Generate system tables first
79
+ const systemYamlTplPath = path.join(__dirname, 'templates', 'SystemDynamoDB_Yaml.ejs');
80
+ const systemYamlTpl = await fs.readFile(systemYamlTplPath, 'utf-8');
81
+ const systemYamlContent = ejs.render(systemYamlTpl, {});
82
+ await fs.writeFile(yamlOutputPath, `${systemYamlContent}\n`, 'utf-8');
83
+ generatedCount += 8; // 8 system tables
84
84
 
85
85
  for (const [tableName, keys] of tables.entries()) {
86
86
  const yamlContent = ejs.render(yamlTpl, {
@@ -2,72 +2,80 @@ import path from 'path';
2
2
  import fs from 'fs/promises';
3
3
  import { fileURLToPath } from 'url';
4
4
  import ejs from 'ejs';
5
+
5
6
  import { getFlowsForGeneration } from '../../app/src/generatedCode/Flow/_shared/shared/flowSelection.js';
6
7
 
7
8
  const __filename = fileURLToPath(import.meta.url);
8
9
  const __dirname = path.dirname(__filename);
9
10
 
10
11
  async function appendToFile(filePath, content) {
11
- try {
12
- await fs.mkdir(path.dirname(filePath), { recursive: true });
13
- await fs.appendFile(filePath, content + '\n', 'utf-8');
14
- } catch (error) {
15
- console.error(`[Error] Failed to append to file: ${filePath}`, error);
16
- }
12
+ try {
13
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
14
+ await fs.appendFile(filePath, `${content}\n`, 'utf-8');
15
+ } catch (error) {
16
+ console.error(`[Error] Failed to append file: ${filePath}`, error);
17
+ }
17
18
  }
18
19
 
19
20
  export async function generateFlowOut(allSchemas, options) {
20
- console.log(' [FlowOutGenerator] Generating Flow Out nodes (SNS Topics)...');
21
-
22
- const resourceYamlDir = path.join(options.outputPath, 'resource', 'sls_yaml', 'generatedCode', 'source');
23
- await fs.mkdir(resourceYamlDir, { recursive: true });
21
+ console.log(' [FlowOutGenerator] Generating Flow Out nodes (SNS Topics)...');
22
+
23
+ const resourceYamlDir = path.join(options.outputPath, 'resource', 'sls_yaml', 'generatedCode', 'source');
24
+ await fs.mkdir(resourceYamlDir, { recursive: true });
25
+
26
+ const yamlTplPath = path.join(__dirname, 'templates', 'FlowOut_Yaml.ejs');
27
+ const yamlTpl = await fs.readFile(yamlTplPath, 'utf-8');
24
28
 
25
- const yamlTplPath = path.join(__dirname, 'templates', 'FlowOut_Yaml.ejs');
26
- const yamlTpl = await fs.readFile(yamlTplPath, 'utf-8');
29
+ let generatedCount = 0;
30
+ const yamlOutputPath = path.join(resourceYamlDir, 'generated-sns-out.yml');
31
+ await fs.writeFile(yamlOutputPath, '', 'utf-8');
27
32
 
28
- let generatedCount = 0;
29
- const yamlOutputPath = path.join(resourceYamlDir, 'generated-sns-out.yml');
33
+ const generatedTopics = new Set();
30
34
 
31
- const flows = getFlowsForGeneration(allSchemas);
32
- for (const flow of flows) {
33
- if (flow.outputTopic === true) {
34
- const upperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1);
35
- const topicName = upperCase(flow.flowTag);
35
+ async function appendTopic(topicName) {
36
+ if (generatedTopics.has(topicName)) return;
36
37
 
37
- const yamlContent = ejs.render(yamlTpl, {
38
- topicName: topicName
39
- });
38
+ generatedTopics.add(topicName);
39
+ const yamlContent = ejs.render(yamlTpl, { topicName });
40
+ await appendToFile(yamlOutputPath, yamlContent);
41
+ generatedCount++;
42
+ }
40
43
 
41
- await appendToFile(yamlOutputPath, yamlContent);
42
- generatedCount++;
43
- }
44
- }
44
+ const flows = getFlowsForGeneration(allSchemas);
45
+ for (const flow of flows) {
46
+ if (flow.outputTopic !== true) continue;
47
+ await appendTopic(flow.flowTag.charAt(0).toUpperCase() + flow.flowTag.slice(1));
48
+ }
45
49
 
46
- const systemTopics = [
47
- 'CreateObjectComplete',
48
- 'DeleteNodeComplete',
49
- 'UpdateNodeComplete',
50
- 'GetNodeComplete',
51
- 'ProcessLogicalComplete',
52
- 'FindDataComplete',
53
- // CRUD Async Topics
54
- 'Create', 'Update', 'Delete',
55
- // Relationship Topics
56
- 'CreateRelationship', 'UpdateRelationship', 'DeleteRelationship',
57
- 'GetRelationship', 'ChangeRelationship', 'MoveRelationship',
58
- // RBAC Topics
59
- 'CreateTargetRole', 'ListTargetRole', 'DeleteTargetRole',
60
- 'CreateRolePermissions', 'ListRolePermissions', 'DeleteRolePermissions',
61
- 'CreateUserRole', 'ListUserInRoles', 'DeleteUserFromRole'
62
- ];
50
+ const systemTopics = [
51
+ 'CreateObjectComplete',
52
+ 'DeleteNodeComplete',
53
+ 'UpdateNodeComplete',
54
+ 'ProcessLogicalComplete',
55
+ 'FindDataComplete',
56
+ 'Create',
57
+ 'Update',
58
+ 'Delete',
59
+ 'CreateRelationship',
60
+ 'UpdateRelationship',
61
+ 'DeleteRelationship',
62
+ 'GetRelationship',
63
+ 'ChangeRelationship',
64
+ 'MoveRelationship',
65
+ 'CreateTargetRole',
66
+ 'ListTargetRole',
67
+ 'DeleteTargetRole',
68
+ 'CreateRolePermissions',
69
+ 'ListRolePermissions',
70
+ 'DeleteRolePermissions',
71
+ 'CreateUserRole',
72
+ 'ListUserInRoles',
73
+ 'DeleteUserFromRole'
74
+ ];
63
75
 
64
- for (const topic of systemTopics) {
65
- const yamlContent = ejs.render(yamlTpl, {
66
- topicName: topic
67
- });
68
- await appendToFile(yamlOutputPath, yamlContent);
69
- generatedCount++;
70
- }
76
+ for (const topic of systemTopics) {
77
+ await appendTopic(topic);
78
+ }
71
79
 
72
- console.log(` [FlowOutGenerator] Generated ${generatedCount} SNS Topics for Flow Out nodes.`);
80
+ console.log(` [FlowOutGenerator] Generated ${generatedCount} SNS Topics for Flow Out nodes.`);
73
81
  }
@@ -2,198 +2,171 @@ import path from 'path';
2
2
  import fs from 'fs/promises';
3
3
  import { fileURLToPath } from 'url';
4
4
  import ejs from 'ejs';
5
+
5
6
  import { getFlowsForGeneration } from '../../app/src/generatedCode/Flow/_shared/shared/flowSelection.js';
6
7
 
7
8
  const __filename = fileURLToPath(import.meta.url);
8
9
  const __dirname = path.dirname(__filename);
9
10
 
10
11
  async function appendToFile(filePath, content) {
11
- try {
12
- await fs.mkdir(path.dirname(filePath), { recursive: true });
13
- await fs.appendFile(filePath, content + '\n', 'utf-8');
14
- } catch (error) {
15
- console.error(`[Error] Failed to append to file: ${filePath}`, error);
16
- }
12
+ try {
13
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
14
+ await fs.appendFile(filePath, `${content}\n`, 'utf-8');
15
+ } catch (error) {
16
+ console.error(`[Error] Failed to append file: ${filePath}`, error);
17
+ }
17
18
  }
18
19
 
19
20
  export async function generateFlowResourceYaml(allSchemas, options) {
20
- console.log(' [FlowResourceYamlGenerator] Generating SQS/SNS YAML resources for flows...');
21
-
22
- const resourceYamlDir = path.join(options.outputPath, 'resource', 'sls_yaml', 'generatedCode', 'source');
23
- await fs.mkdir(resourceYamlDir, { recursive: true });
24
-
25
- const resourceYamlTplPath = path.join(__dirname, 'templates', 'crud', 'ResourceYaml.ejs');
26
- const resourceBeforeLogicalYamlTplPath = path.join(__dirname, 'templates', 'crud', 'ResourceBeforeLogicalYaml.ejs');
27
-
28
- const resourceYamlTpl = await fs.readFile(resourceYamlTplPath, 'utf-8');
29
- const resourceBeforeLogicalYamlTpl = await fs.readFile(resourceBeforeLogicalYamlTplPath, 'utf-8');
30
-
31
- let generatedCount = 0;
32
- const yamlOutputPath = path.join(resourceYamlDir, 'generated-sns-in-sqs.yml');
33
-
34
- const flows = getFlowsForGeneration(allSchemas);
35
- for (const flow of flows) {
36
- const flowTag = flow.flowTag;
37
-
38
- // Generate In-Queue resources for any flow that listens on its own topic
39
- if (flow.event && flow.event.includes('ownTopic')) {
40
- // Render main ResourceYaml.ejs (e.g., InCreate, CreateHdrSqs)
41
- const yamlContent = ejs.render(resourceYamlTpl, {
42
- queueName: flowTag
43
- });
44
- await appendToFile(yamlOutputPath, yamlContent);
45
- generatedCount++;
46
-
47
- // Check if there are beforeLogical configs for this flow
48
- const generateCodeConfigs = flows
49
- .filter(f => f.generatedCodeConfig && f.generatedCodeConfig.generatedCodeTag.toLowerCase() === flowTag.toLowerCase())
50
- .map(f => ({
51
- ...f.generatedCodeConfig,
52
- flowTag: f.flowTag
53
- }));
54
-
55
- const beforeLogicalConfigs = generateCodeConfigs.filter(c => c.codeHookTag === 'beforeLogical');
56
-
57
- if (beforeLogicalConfigs.length > 0) {
58
- // Collect unique flowTags (like ValidateUnitTrackedProperty) from the beforeLogical configs
59
- const uniqueFlowTags = [...new Set(beforeLogicalConfigs.map(c => c.flowTag))];
60
-
61
- const beforeLogicalYamlContent = ejs.render(resourceBeforeLogicalYamlTpl, {
62
- queueName: `${flowTag}BeforeLogical`,
63
- flowTags: uniqueFlowTags
64
- });
65
-
66
- await appendToFile(yamlOutputPath, beforeLogicalYamlContent);
67
- generatedCount++;
68
- }
69
- } else if (flow.event && flow.event.includes('extTopic')) {
70
- // Custom flows using extTopic do not have ownTopic, but need an entry queue
71
- const resourceSqsYamlTplPath = path.join(__dirname, 'templates', 'crud', 'ResourceSqsYaml.ejs');
72
- const resourceSqsYamlTpl = await fs.readFile(resourceSqsYamlTplPath, 'utf-8');
73
- const yamlContent = ejs.render(resourceSqsYamlTpl, {
74
- queueName: `Process${flowTag}Sqs`
75
- });
76
- await appendToFile(yamlOutputPath, yamlContent);
77
- generatedCount++;
78
- }
79
-
80
- // Generate Complete queues for custom FlowSchema
81
- // Also generate queues for any flowStep that uses sqs, dsq, or extTopic
82
- if (flow.flowSteps && Array.isArray(flow.flowSteps)) {
83
- for (const stepConfig of flow.flowSteps) {
84
- const stepName = stepConfig.stepName;
85
- if (stepName === 'InTag') continue;
86
-
87
- const events = stepConfig.event || ['sqs'];
88
- for (const ev of events) {
89
- let queueName = flowTag + stepName;
90
- if (stepName === 'Complete') {
91
- queueName = flowTag + 'Complete';
92
- }
93
-
94
- if (ev === 'sqs' || ev === 'ownTopic') {
95
- // sqs and ownTopic create SNS Topic + Subscription + SQS Queue
96
- const yamlContent = ejs.render(resourceYamlTpl, {
97
- queueName: queueName
98
- });
99
- await appendToFile(yamlOutputPath, yamlContent);
100
- generatedCount++;
101
- } else if (ev === 'dsq' || ev === 'extTopic') {
102
- // extTopic and dsq create ONLY SQS Queue
103
- const resourceSqsYamlTplPath = path.join(__dirname, 'templates', 'crud', 'ResourceSqsYaml.ejs');
104
- const resourceSqsYamlTpl = await fs.readFile(resourceSqsYamlTplPath, 'utf-8');
105
- const yamlContent = ejs.render(resourceSqsYamlTpl, {
106
- queueName: queueName
107
- });
108
- await appendToFile(yamlOutputPath, yamlContent);
109
- generatedCount++;
110
- }
111
- }
112
- }
113
- }
21
+ console.log(' [FlowResourceYamlGenerator] Generating SQS/SNS YAML resources for flows...');
114
22
 
115
- // Generate SQS queues for S3 Event flows
116
- if (flow.event && flow.event.includes('s3')) {
117
- const queueNames = [
118
- `${flowTag}CreatePreSignUrlSqs`,
119
- `${flowTag}ReservedLimitSqs`
120
- ];
121
-
122
- for (const qName of queueNames) {
123
- const resourceSqsYamlTplPath = path.join(__dirname, 'templates', 'crud', 'ResourceSqsYaml.ejs');
124
- const resourceSqsYamlTpl = await fs.readFile(resourceSqsYamlTplPath, 'utf-8');
125
- const yamlContent = ejs.render(resourceSqsYamlTpl, {
126
- queueName: qName
127
- });
128
- await appendToFile(yamlOutputPath, yamlContent);
129
- generatedCount++;
130
- }
131
- }
23
+ const resourceYamlDir = path.join(options.outputPath, 'resource', 'sls_yaml', 'generatedCode', 'source');
24
+ await fs.mkdir(resourceYamlDir, { recursive: true });
132
25
 
133
- // Generate Dsq queues for Relationship
134
- if (flow.isDynamicRelationshipType) {
135
- // Usually Relationship has dead-letter queue processing
136
- const resourceSqsYamlTplPath = path.join(__dirname, 'templates', 'crud', 'ResourceSqsYaml.ejs');
137
- const resourceSqsYamlTpl = await fs.readFile(resourceSqsYamlTplPath, 'utf-8');
138
- const queueName = flowTag + 'Dsq';
139
- const yamlContent = ejs.render(resourceSqsYamlTpl, {
140
- queueName: queueName
141
- });
142
- await appendToFile(yamlOutputPath, yamlContent);
143
- generatedCount++;
144
- }
26
+ const resourceYamlTplPath = path.join(__dirname, 'templates', 'crud', 'ResourceYaml.ejs');
27
+ const resourceBeforeLogicalYamlTplPath = path.join(__dirname, 'templates', 'crud', 'ResourceBeforeLogicalYaml.ejs');
28
+ const resourceSqsYamlTplPath = path.join(__dirname, 'templates', 'crud', 'ResourceSqsYaml.ejs');
29
+
30
+ const resourceYamlTpl = await fs.readFile(resourceYamlTplPath, 'utf-8');
31
+ const resourceBeforeLogicalYamlTpl = await fs.readFile(resourceBeforeLogicalYamlTplPath, 'utf-8');
32
+ const resourceSqsYamlTpl = await fs.readFile(resourceSqsYamlTplPath, 'utf-8');
33
+
34
+ let generatedCount = 0;
35
+ const yamlOutputPath = path.join(resourceYamlDir, 'generated-sns-in-sqs.yml');
36
+ await fs.writeFile(yamlOutputPath, '', 'utf-8');
37
+
38
+ const generatedResources = new Set();
39
+
40
+ async function appendUnique(resourceKey, template, templateData) {
41
+ if (generatedResources.has(resourceKey)) return;
42
+
43
+ generatedResources.add(resourceKey);
44
+ const yamlContent = ejs.render(template, templateData);
45
+ await appendToFile(yamlOutputPath, yamlContent);
46
+ generatedCount++;
47
+ }
48
+
49
+ const flows = getFlowsForGeneration(allSchemas);
50
+
51
+ for (const flow of flows) {
52
+ const flowTag = flow.flowTag;
53
+
54
+ if (flow.event && flow.event.includes('ownTopic')) {
55
+ await appendUnique(flowTag, resourceYamlTpl, { queueName: flowTag });
56
+
57
+ const generateCodeConfigs = flows
58
+ .filter((candidate) => candidate.generatedCodeConfig
59
+ && candidate.generatedCodeConfig.generatedCodeTag?.toLowerCase() === flowTag.toLowerCase())
60
+ .map((candidate) => ({
61
+ ...candidate.generatedCodeConfig,
62
+ flowTag: candidate.flowTag
63
+ }));
64
+
65
+ const beforeLogicalConfigs = generateCodeConfigs.filter((config) => config.codeHookTag === 'beforeLogical');
66
+ if (beforeLogicalConfigs.length > 0) {
67
+ const uniqueFlowTags = [...new Set(beforeLogicalConfigs.map((config) => config.flowTag))];
68
+ await appendUnique(`${flowTag}BeforeLogical`, resourceBeforeLogicalYamlTpl, {
69
+ queueName: `${flowTag}BeforeLogical`,
70
+ flowTags: uniqueFlowTags
71
+ });
72
+ }
73
+ } else if (flow.event && flow.event.includes('extTopic')) {
74
+ await appendUnique(`Process${flowTag}HdrSqs`, resourceSqsYamlTpl, {
75
+ queueName: `Process${flowTag}HdrSqs`
76
+ });
145
77
  }
146
78
 
147
- // Add hardcoded ownTopic flows for CRUD, RBAC, Relationship
148
- const systemOwnTopicFlows = [
149
- 'Create', 'Update', 'Delete', // CRUD
150
- 'CreateRelationship', 'UpdateRelationship', 'DeleteRelationship', 'ChangeRelationship', 'MoveRelationship', // Relationship
151
- 'CreateTargetRole', 'DeleteTargetRole', 'CreateRolePermissions', 'DeleteRolePermissions', 'CreateUserRole', 'DeleteUserFromRole' // RBAC
152
- ];
153
- for (const flowTag of systemOwnTopicFlows) {
154
- const yamlContent = ejs.render(resourceYamlTpl, { queueName: flowTag });
155
- await appendToFile(yamlOutputPath, yamlContent);
156
- generatedCount++;
157
-
158
- // BeforeLogical queues for CRUD
159
- if (['Create', 'Update', 'Delete'].includes(flowTag)) {
160
- // Since we don't have beforeLogical configs here, we assume we might need them or we can just generate a generic one?
161
- // For now, only custom flows generate beforeLogical dynamically above.
79
+ if (Array.isArray(flow.flowSteps)) {
80
+ for (const stepConfig of flow.flowSteps) {
81
+ const stepName = stepConfig.stepName;
82
+ if (stepName === 'InTag') continue;
83
+
84
+ const events = stepConfig.event || ['sqs'];
85
+ for (const ev of events) {
86
+ let queueName = `${flowTag}${stepName}`;
87
+ if (stepName === 'Complete') {
88
+ queueName = `${flowTag}Complete`;
89
+ }
90
+
91
+ if (ev === 'sqs' || ev === 'ownTopic') {
92
+ await appendUnique(queueName, resourceYamlTpl, { queueName });
93
+ } else if (ev === 'dsq' || ev === 'extTopic') {
94
+ await appendUnique(`${queueName}HdrDsq`, resourceSqsYamlTpl, {
95
+ queueName: `${queueName}HdrDsq`
96
+ });
97
+ }
162
98
  }
99
+ }
163
100
  }
164
101
 
165
- // Generate Dsq queues for Relationship
166
- const relationshipActions = ['CreateRelationship', 'UpdateRelationship', 'DeleteRelationship', 'GetRelationship', 'ChangeRelationship', 'MoveRelationship'];
167
- const resourceSqsYamlTplPath = path.join(__dirname, 'templates', 'crud', 'ResourceSqsYaml.ejs');
168
- const resourceSqsYamlTpl = await fs.readFile(resourceSqsYamlTplPath, 'utf-8');
169
- for (const action of relationshipActions) {
170
- const yamlContent = ejs.render(resourceSqsYamlTpl, { queueName: action + 'Dsq' });
171
- await appendToFile(yamlOutputPath, yamlContent);
172
- generatedCount++;
173
- }
102
+ if (flow.event && flow.event.includes('s3')) {
103
+ const queueNames = [
104
+ `${flowTag}CreatePreSignUrlHdrSqs`,
105
+ `${flowTag}ReservedLimitHdrSqs`
106
+ ];
174
107
 
175
- // Add System Queues
176
- const systemQueues = [
177
- 'ProcessLogical',
178
- 'PaginateProcessLogical',
179
- 'FindData',
180
- 'Register',
181
- 'WebSocket',
182
- 'CreateObjectComplete',
183
- 'UpdateNodeComplete',
184
- 'DeleteNodeComplete',
185
- 'GetNodeComplete'
186
- ];
187
-
188
- const resourceSqsYamlTplGlobal = await fs.readFile(resourceSqsYamlTplPath, 'utf-8');
189
-
190
- for (const sysQueue of systemQueues) {
191
- const yamlContent = ejs.render(resourceSqsYamlTplGlobal, {
192
- queueName: sysQueue
193
- });
194
- await appendToFile(yamlOutputPath, yamlContent);
195
- generatedCount++;
108
+ for (const queueName of queueNames) {
109
+ await appendUnique(queueName, resourceSqsYamlTpl, { queueName });
110
+ }
196
111
  }
197
112
 
198
- console.log(` [FlowResourceYamlGenerator] Appended ${generatedCount} sections to generated-sns-in-sqs.yml.`);
113
+ if (flow.isDynamicRelationshipType) {
114
+ const queueName = `${flowTag}HdrDsq`;
115
+ await appendUnique(queueName, resourceSqsYamlTpl, { queueName });
116
+ }
117
+ }
118
+
119
+ const systemOwnTopicFlows = [
120
+ 'Create',
121
+ 'Update',
122
+ 'Delete',
123
+ 'CreateRelationship',
124
+ 'UpdateRelationship',
125
+ 'DeleteRelationship',
126
+ 'ChangeRelationship',
127
+ 'MoveRelationship',
128
+ 'CreateTargetRole',
129
+ 'DeleteTargetRole',
130
+ 'CreateRolePermissions',
131
+ 'DeleteRolePermissions',
132
+ 'CreateUserRole',
133
+ 'DeleteUserFromRole'
134
+ ];
135
+
136
+ for (const flowTag of systemOwnTopicFlows) {
137
+ await appendUnique(flowTag, resourceYamlTpl, { queueName: flowTag });
138
+ }
139
+
140
+ const relationshipActions = [
141
+ 'CreateRelationship',
142
+ 'UpdateRelationship',
143
+ 'DeleteRelationship',
144
+ 'GetRelationship',
145
+ 'ChangeRelationship',
146
+ 'MoveRelationship'
147
+ ];
148
+
149
+ for (const action of relationshipActions) {
150
+ const queueName = `${action}HdrDsq`;
151
+ await appendUnique(queueName, resourceSqsYamlTpl, { queueName });
152
+ }
153
+
154
+ const systemQueues = [
155
+ 'ProcessLogical',
156
+ 'PaginateProcessLogical',
157
+ 'FindData',
158
+ 'Register',
159
+ 'WebSocket',
160
+ 'CreateObjectComplete',
161
+ 'UpdateNodeComplete',
162
+ 'DeleteNodeComplete',
163
+ 'GetNodeComplete'
164
+ ];
165
+
166
+ for (const sysQueue of systemQueues) {
167
+ const queueName = `${sysQueue}HdrSqs`;
168
+ await appendUnique(queueName, resourceSqsYamlTpl, { queueName });
169
+ }
170
+
171
+ console.log(` [FlowResourceYamlGenerator] Appended ${generatedCount} sections to generated-sns-in-sqs.yml.`);
199
172
  }
@@ -10,30 +10,30 @@
10
10
  Type: AWS::SNS::Subscription
11
11
  Properties:
12
12
  TopicArn: !Ref In<%- queueName %>
13
- Endpoint: "arn:aws:sqs:${self:custom.iz_region}:${self:custom.iz_accountId}:${self:custom.iz_resourcePrefix}<%- queueName %>Sqs"
13
+ Endpoint: "arn:aws:sqs:${self:custom.iz_region}:${self:custom.iz_accountId}:${self:custom.iz_resourcePrefix}<%- queueName %>HdrSqs"
14
14
  Protocol: "sqs"
15
15
 
16
16
  ##===== [Queue]
17
- <%- queueName %>Sqs:
17
+ <%- queueName %>HdrSqs:
18
18
  Type: "AWS::SQS::Queue"
19
19
  Properties:
20
- QueueName: ${self:custom.iz_resourcePrefix}<%- queueName %>Sqs
20
+ QueueName: ${self:custom.iz_resourcePrefix}<%- queueName %>HdrSqs
21
21
  RedrivePolicy:
22
22
  deadLetterTargetArn:
23
23
  Fn::GetAtt:
24
- - <%- queueName %>SqsDLQ
24
+ - <%- queueName %>HdrSqsDLQ
25
25
  - Arn
26
26
  maxReceiveCount: 3
27
27
  VisibilityTimeout: 120
28
28
 
29
29
  ##===== [Queue DLQ]
30
- <%- queueName %>SqsDLQ:
30
+ <%- queueName %>HdrSqsDLQ:
31
31
  Type: AWS::SQS::Queue
32
32
  Properties:
33
- QueueName: ${self:custom.iz_resourcePrefix}<%- queueName %>SqsDLQ
33
+ QueueName: ${self:custom.iz_resourcePrefix}<%- queueName %>HdrSqsDLQ
34
34
 
35
35
  ##===== [Queue Policy]
36
- <%- queueName %>SqsPolicy:
36
+ <%- queueName %>HdrSqsPolicy:
37
37
  Type: AWS::SQS::QueuePolicy
38
38
  Properties:
39
39
  PolicyDocument:
@@ -44,11 +44,11 @@
44
44
  Principal: "*"
45
45
  Resource:
46
46
  Fn::GetAtt:
47
- - <%- queueName %>Sqs
47
+ - <%- queueName %>HdrSqs
48
48
  - Arn
49
49
  Action: "SQS:SendMessage"
50
50
  Queues:
51
- - Ref: <%- queueName %>Sqs
51
+ - Ref: <%- queueName %>HdrSqs
52
52
 
53
53
  #<#<%- queueName %>QueueSetting#>
54
- #<#/<%- queueName %>QueueSetting#>
54
+ #<#/<%- queueName %>QueueSetting#>
@@ -5,8 +5,6 @@ import { parseObjectSchemas } from './parsers/objectSchemaParser.js';
5
5
  import { parseRelationshipSchemas } from './parsers/relationshipSchemaParser.js';
6
6
  import { parseFlowSchemas } from './parsers/flowSchemaParser.js';
7
7
 
8
-
9
-
10
8
  import { generateFlowEntryPoint } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowEntryPointBase.js';
11
9
  import { generateFlowMainFunction } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowMainFunctionBase.js';
12
10
  import { generateFlowSteps } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepBase.js';
@@ -33,85 +31,106 @@ import { generateFindDataYaml } from './codeGenerators/app/sls_yaml/FindDataYaml
33
31
  import { policyRegistry } from './codeGenerators/app/sls_yaml/_policy/PolicyRegistry.js';
34
32
  import { emitManagedPolicies } from './codeGenerators/app/sls_yaml/_policy/PolicyEmitter.js';
35
33
 
36
- export async function generateCode(rootPath, options) {
34
+ function createGeneratorOptions(rootPath, options = {}) {
35
+ return {
36
+ outputPath: options.outputPath || rootPath
37
+ };
38
+ }
39
+
40
+ export async function generateCode(rootPath, options = {}) {
37
41
  console.log(
38
42
  '[INFO] [generateCode] STATUS=STARTING | Generating code files from all schemas...'
39
43
  );
40
44
 
45
+ const generatorOptions = createGeneratorOptions(rootPath, options);
46
+ const { outputPath } = generatorOptions;
47
+
41
48
  // Clean workspace
42
- const outputPath = options.outputPath || rootPath;
43
- const generatedCodePath = path.join(outputPath, 'app', 'src', 'generatedCode');
49
+ const generatedCodePath = path.join(
50
+ outputPath,
51
+ 'app',
52
+ 'src',
53
+ 'generatedCode'
54
+ );
44
55
 
45
56
  // Backup existing hook files to prevent them from being wiped out
46
- const hookBackups = [];
47
- const hookDirs = new Set();
48
-
49
- function getHookTagMatchers(filePath) {
50
- if (filePath.endsWith('.js')) {
51
- return {
52
- anyTag: /\/\/\(<(\w+)>\)([\s\S]*?)\/\/\(<\/\1>\)/gm,
53
- tagPattern: (name) => new RegExp(
54
- `\\/\\/\\(<${name}>\\)([\\s\\S]*?)\\/\\/\\(<\\/${name}>\\)`,
55
- 'gm'
56
- )
57
- };
58
- }
59
-
60
- if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
61
- return {
62
- anyTag: /#<#(\w+)#>([\s\S]*?)#<#\/\1#>/gm,
63
- tagPattern: (name) => new RegExp(
64
- `#<#${name}#>([\\s\\S]*?)#<#\\/${name}#>`,
65
- 'gm'
66
- )
67
- };
68
- }
69
-
70
- return null;
71
- }
72
-
73
- function mergeHookIntoSource(sourceContent, hookContent, filePath) {
74
- const matchers = getHookTagMatchers(filePath);
75
- if (!matchers) return hookContent;
76
-
77
- return sourceContent.replace(matchers.anyTag, (match, tagName) => (
78
- hookContent.match(matchers.tagPattern(tagName))?.[0] ?? match
79
- ));
80
- }
81
-
82
- function collectHookBackups(currentDir) {
83
- if (!fs.existsSync(currentDir)) return;
84
-
85
- const items = fs.readdirSync(currentDir, { withFileTypes: true });
86
- const hasSource = items.some((item) => item.isDirectory() && item.name === 'source');
87
- const hasHook = items.some((item) => item.isDirectory() && item.name === 'hook');
88
-
89
- if (hasSource && hasHook) {
90
- const hookPath = path.join(currentDir, 'hook');
91
- hookDirs.add(path.relative(outputPath, hookPath));
92
- const hookItems = fs.readdirSync(hookPath, { withFileTypes: true });
93
-
94
- for (const hookItem of hookItems) {
95
- if (!hookItem.isFile()) continue;
96
- const hookFilePath = path.join(hookPath, hookItem.name);
97
- hookBackups.push({
98
- relativePath: path.relative(outputPath, hookPath),
99
- name: hookItem.name,
100
- content: fs.readFileSync(hookFilePath)
101
- });
102
- }
103
- }
104
-
105
- for (const item of items) {
106
- if (!item.isDirectory() || item.name === 'source' || item.name === 'hook') continue;
107
- collectHookBackups(path.join(currentDir, item.name));
108
- }
109
- }
110
-
111
- console.log('[INFO] [generateCode] STATUS=BACKUP | Backing up existing hook files...');
112
- collectHookBackups(outputPath);
113
-
114
- console.log(
57
+ const hookBackups = [];
58
+ const hookDirs = new Set();
59
+
60
+ function getHookTagMatchers(filePath) {
61
+ if (filePath.endsWith('.js')) {
62
+ return {
63
+ anyTag: /\/\/\(<(\w+)>\)([\s\S]*?)\/\/\(<\/\1>\)/gm,
64
+ tagPattern: name =>
65
+ new RegExp(
66
+ `\\/\\/\\(<${name}>\\)([\\s\\S]*?)\\/\\/\\(<\\/${name}>\\)`,
67
+ 'gm'
68
+ )
69
+ };
70
+ }
71
+
72
+ if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
73
+ return {
74
+ anyTag: /#<#(\w+)#>([\s\S]*?)#<#\/\1#>/gm,
75
+ tagPattern: name =>
76
+ new RegExp(`#<#${name}#>([\\s\\S]*?)#<#\\/${name}#>`, 'gm')
77
+ };
78
+ }
79
+
80
+ return null;
81
+ }
82
+
83
+ function mergeHookIntoSource(sourceContent, hookContent, filePath) {
84
+ const matchers = getHookTagMatchers(filePath);
85
+ if (!matchers) return hookContent;
86
+
87
+ return sourceContent.replace(
88
+ matchers.anyTag,
89
+ (match, tagName) =>
90
+ hookContent.match(matchers.tagPattern(tagName))?.[0] ?? match
91
+ );
92
+ }
93
+
94
+ function collectHookBackups(currentDir) {
95
+ if (!fs.existsSync(currentDir)) return;
96
+
97
+ const items = fs.readdirSync(currentDir, { withFileTypes: true });
98
+ const hasSource = items.some(
99
+ item => item.isDirectory() && item.name === 'source'
100
+ );
101
+ const hasHook = items.some(
102
+ item => item.isDirectory() && item.name === 'hook'
103
+ );
104
+
105
+ if (hasSource && hasHook) {
106
+ const hookPath = path.join(currentDir, 'hook');
107
+ hookDirs.add(path.relative(outputPath, hookPath));
108
+ const hookItems = fs.readdirSync(hookPath, { withFileTypes: true });
109
+
110
+ for (const hookItem of hookItems) {
111
+ if (!hookItem.isFile()) continue;
112
+ const hookFilePath = path.join(hookPath, hookItem.name);
113
+ hookBackups.push({
114
+ relativePath: path.relative(outputPath, hookPath),
115
+ name: hookItem.name,
116
+ content: fs.readFileSync(hookFilePath)
117
+ });
118
+ }
119
+ }
120
+
121
+ for (const item of items) {
122
+ if (!item.isDirectory() || item.name === 'source' || item.name === 'hook')
123
+ continue;
124
+ collectHookBackups(path.join(currentDir, item.name));
125
+ }
126
+ }
127
+
128
+ console.log(
129
+ '[INFO] [generateCode] STATUS=BACKUP | Backing up existing hook files...'
130
+ );
131
+ collectHookBackups(outputPath);
132
+
133
+ console.log(
115
134
  '[INFO] [generateCode] STATUS=CLEANUP | Removing old generatedCode and resource directories...'
116
135
  );
117
136
  await fs.promises.rm(generatedCodePath, {
@@ -150,8 +169,6 @@ console.log(
150
169
  console.log('[INFO] [generateCode] STATUS=VALIDATING | Validating Flows...');
151
170
  validateFlowsForGeneration(allSchemas.flowsForGeneration);
152
171
 
153
- const generatorOptions = { outputPath: options.outputPath || rootPath };
154
-
155
172
  // 2. Generation Pipeline
156
173
  await generateEndpointsFlow(allSchemas, generatorOptions);
157
174
  await generateFlowMainFunction(allSchemas, generatorOptions);
@@ -161,11 +178,19 @@ console.log(
161
178
  await generateFlowResourceYaml(allSchemas, generatorOptions);
162
179
  await generateRbacFlows(allSchemas, generatorOptions);
163
180
  await generateRelationshipFlows(allSchemas, generatorOptions);
164
- await generateWebSocket(allSchemas.flowsForGeneration, '', generatorOptions.outputPath);
165
- await generateRegisterFlow(allSchemas.flowsForGeneration, '', generatorOptions.outputPath);
181
+ await generateWebSocket(
182
+ allSchemas.flowsForGeneration,
183
+ '',
184
+ generatorOptions.outputPath
185
+ );
186
+ await generateRegisterFlow(
187
+ allSchemas.flowsForGeneration,
188
+ '',
189
+ generatorOptions.outputPath
190
+ );
166
191
 
167
192
  // Call FunctionYamlGenerator after all Handlers have been generated
168
- await generateFunctionYaml(allSchemas, options);
193
+ await generateFunctionYaml(allSchemas, generatorOptions);
169
194
  await generateDynamoDBTables(allSchemas, generatorOptions);
170
195
  await generateFindData(allSchemas, generatorOptions);
171
196
  await generateCodeLibs(generatorOptions);
@@ -178,69 +203,76 @@ console.log(
178
203
  await generateInitialSetup(allSchemas, generatorOptions);
179
204
 
180
205
  // 3. Hook Integration
181
- console.log(
182
- '[INFO] [generateCode] STATUS=HOOKS | Processing hook folders beside source folders...'
183
- );
184
-
185
- function processSourceHooks(currentDir) {
186
- if (!fs.existsSync(currentDir)) return;
187
-
188
- const items = fs.readdirSync(currentDir, { withFileTypes: true });
189
- const hasSource = items.some((item) => item.isDirectory() && item.name === 'source');
190
- const hasHook = items.some((item) => item.isDirectory() && item.name === 'hook');
191
-
192
- if (hasSource && hasHook) {
193
- const sourcePath = path.join(currentDir, 'source');
194
- const hookPath = path.join(currentDir, 'hook');
195
- const hookItems = fs.readdirSync(hookPath, { withFileTypes: true });
196
-
197
- for (const hookItem of hookItems) {
198
- if (!hookItem.isFile()) continue;
199
-
200
- const hookFile = path.join(hookPath, hookItem.name);
201
- const sourceFile = path.join(sourcePath, hookItem.name);
202
-
203
- if (!fs.existsSync(sourceFile)) {
204
- fs.copyFileSync(hookFile, sourceFile);
205
- console.log(
206
- `[INFO] [generateCode] HOOK APPLIED: Created ${hookItem.name} from hook.`
207
- );
208
- continue;
209
- }
210
-
211
- const mergedContent = mergeHookIntoSource(
212
- fs.readFileSync(sourceFile, 'utf8'),
213
- fs.readFileSync(hookFile, 'utf8'),
214
- sourceFile
215
- );
216
- fs.writeFileSync(sourceFile, mergedContent);
217
- console.log(
218
- `[INFO] [generateCode] HOOK APPLIED: Merged ${hookItem.name} into source.`
219
- );
220
- }
221
- } else if (hasSource) {
222
- fs.mkdirSync(path.join(currentDir, 'hook'), { recursive: true });
223
- }
224
-
225
- for (const item of items) {
226
- if (!item.isDirectory() || item.name === 'source' || item.name === 'hook') continue;
227
- processSourceHooks(path.join(currentDir, item.name));
228
- }
229
- }
230
-
231
- // Restore backed-up hooks
232
- if (hookDirs.size > 0 || hookBackups.length > 0) {
233
- console.log(`[INFO] [generateCode] STATUS=RESTORE | Restoring ${hookBackups.length} hook files...`);
234
- for (const relativeHookDir of hookDirs) {
235
- fs.mkdirSync(path.join(outputPath, relativeHookDir), { recursive: true });
236
- }
237
- for (const backup of hookBackups) {
238
- const hookDir = path.join(outputPath, backup.relativePath);
239
- fs.writeFileSync(path.join(hookDir, backup.name), backup.content);
240
- }
241
- }
242
-
243
- processSourceHooks(generatorOptions.outputPath);
206
+ console.log(
207
+ '[INFO] [generateCode] STATUS=HOOKS | Processing hook folders beside source folders...'
208
+ );
209
+
210
+ function processSourceHooks(currentDir) {
211
+ if (!fs.existsSync(currentDir)) return;
212
+
213
+ const items = fs.readdirSync(currentDir, { withFileTypes: true });
214
+ const hasSource = items.some(
215
+ item => item.isDirectory() && item.name === 'source'
216
+ );
217
+ const hasHook = items.some(
218
+ item => item.isDirectory() && item.name === 'hook'
219
+ );
220
+
221
+ if (hasSource && hasHook) {
222
+ const sourcePath = path.join(currentDir, 'source');
223
+ const hookPath = path.join(currentDir, 'hook');
224
+ const hookItems = fs.readdirSync(hookPath, { withFileTypes: true });
225
+
226
+ for (const hookItem of hookItems) {
227
+ if (!hookItem.isFile()) continue;
228
+
229
+ const hookFile = path.join(hookPath, hookItem.name);
230
+ const sourceFile = path.join(sourcePath, hookItem.name);
231
+
232
+ if (!fs.existsSync(sourceFile)) {
233
+ fs.copyFileSync(hookFile, sourceFile);
234
+ console.log(
235
+ `[INFO] [generateCode] HOOK APPLIED: Created ${hookItem.name} from hook.`
236
+ );
237
+ continue;
238
+ }
239
+
240
+ const mergedContent = mergeHookIntoSource(
241
+ fs.readFileSync(sourceFile, 'utf8'),
242
+ fs.readFileSync(hookFile, 'utf8'),
243
+ sourceFile
244
+ );
245
+ fs.writeFileSync(sourceFile, mergedContent);
246
+ console.log(
247
+ `[INFO] [generateCode] HOOK APPLIED: Merged ${hookItem.name} into source.`
248
+ );
249
+ }
250
+ } else if (hasSource) {
251
+ fs.mkdirSync(path.join(currentDir, 'hook'), { recursive: true });
252
+ }
253
+
254
+ for (const item of items) {
255
+ if (!item.isDirectory() || item.name === 'source' || item.name === 'hook')
256
+ continue;
257
+ processSourceHooks(path.join(currentDir, item.name));
258
+ }
259
+ }
260
+
261
+ // Restore backed-up hooks
262
+ if (hookDirs.size > 0 || hookBackups.length > 0) {
263
+ console.log(
264
+ `[INFO] [generateCode] STATUS=RESTORE | Restoring ${hookBackups.length} hook files...`
265
+ );
266
+ for (const relativeHookDir of hookDirs) {
267
+ fs.mkdirSync(path.join(outputPath, relativeHookDir), { recursive: true });
268
+ }
269
+ for (const backup of hookBackups) {
270
+ const hookDir = path.join(outputPath, backup.relativePath);
271
+ fs.writeFileSync(path.join(hookDir, backup.name), backup.content);
272
+ }
273
+ }
274
+
275
+ processSourceHooks(generatorOptions.outputPath);
244
276
 
245
277
  console.log(
246
278
  '[INFO] [generateCode] STATUS=FINISHED | Code generation complete.'
@@ -22,18 +22,20 @@ export default {
22
22
  messageAttributes: ['userId', 'correlationId']
23
23
  },
24
24
  <% if (hasBeforeLogicalStep) { %>
25
- {
26
- stepName: 'beforeLogical',
27
- properties: [],
28
- messageAttributes: ['userId', 'correlationId']
29
- },
25
+ {
26
+ stepName: 'beforeLogical',
27
+ event: ['sqs'],
28
+ properties: [],
29
+ messageAttributes: ['userId', 'correlationId']
30
+ },
30
31
  <% } %>
31
32
  <% if (['Create', 'Update', 'Delete'].includes(action)) { %>
32
- {
33
- stepName: 'Complete',
34
- properties: [],
35
- messageAttributes: ['userId', 'correlationId']
36
- }
33
+ {
34
+ stepName: 'Complete',
35
+ event: ['sqs'],
36
+ properties: [],
37
+ messageAttributes: ['userId', 'correlationId']
38
+ }
37
39
  <% } %>
38
40
  ]
39
41
  };
@@ -22,11 +22,12 @@ export default {
22
22
  messageAttributes: ['userId', 'correlationId']
23
23
  }
24
24
  <% if (hasCompleteStep) { %>,
25
- {
26
- stepName: 'Complete',
27
- properties: [],
28
- messageAttributes: ['userId', 'correlationId']
29
- }
25
+ {
26
+ stepName: 'Complete',
27
+ event: ['sqs'],
28
+ properties: [],
29
+ messageAttributes: ['userId', 'correlationId']
30
+ }
30
31
  <% } %>
31
32
  ]
32
33
  };