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

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.62",
3
+ "version": "1.0.64",
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": {
@@ -10,7 +10,7 @@ export function generatedLambdaRole() {
10
10
  const externalLambdaRole = [
11
11
  <% datas.forEach((data, dataIdx) => { -%>
12
12
  {
13
- roleName: '<%- data.objectType %>',
13
+ roleName: '<%- data.objectType.replace(/Role$/, '') %>',
14
14
  statement: [
15
15
  <% data.additionalResourcePermission.forEach((resourcePermission, resourceIdx) => { -%>
16
16
  {
@@ -80,7 +80,7 @@ export async function generateEndpointsFlow(allSchemas, options) {
80
80
  const beforeLogicalConfigs = generateCode.filter(c => c.codeHookTag === 'beforeLogical');
81
81
  if (beforeLogicalConfigs.length > 0) {
82
82
  const beforeLogicalTemplateStr = await fs.readFile(
83
- path.join(templateDir, 'FlowEndpointBeforeLogical_Main.ejs'),
83
+ path.join(templateDir, 'step', 'FlowEndpointBeforeLogical_Main.ejs'),
84
84
  'utf-8'
85
85
  );
86
86
  const beforeLogicalFileName = `${upperAction}BeforeLogical_Main`;
@@ -41,9 +41,9 @@ const { TOPIC_NAME_GENERATE_CODE, TOPIC_NAME_GRAPH_HANDLER } = consts;
41
41
  const PREFIX = {
42
42
  ONE: 'one',
43
43
  MANY: 'many',
44
- CREATE_OBJECT_ASYNC: 'createObjectAsyncEndpoint',
45
- CREATE_OBJECT_ASYNC_COMPLETE: 'createObjectAsyncEndpointComplete',
46
- CREATE_OBJECT_EXTERNAL_TOPIC: 'createObjectExternalTopicEndpoint'
44
+ CREATE_OBJECT_ASYNC: 'CreateAsyncEndpoint',
45
+ CREATE_OBJECT_ASYNC_COMPLETE: 'CreateAsyncEndpointComplete',
46
+ CREATE_OBJECT_EXTERNAL_TOPIC: 'CreateExternalTopicEndpoint'
47
47
  };
48
48
 
49
49
  /**
@@ -256,13 +256,13 @@ export async function <%- flowTag %>MainLogical(
256
256
 
257
257
  // ── Create awaiting multiple steps ────────────────────────────────────────
258
258
  _izContext.logger.debug('not api will create awaitingStep');
259
- await asyncFlowSharedLib.createAwaitingMultipleStepsWithAdditionalAttributes(
259
+ await asyncFlowSharedLib.createAwaitingMultipleSteps(
260
260
  _izContext,
261
- allAwaitingStepsId,
262
261
  asyncFlowSharedLib.createPendingStepId(
263
262
  _izContext.uniqueRequestId,
264
263
  PREFIX.CREATE_OBJECT_ASYNC_COMPLETE
265
- )
264
+ ),
265
+ allAwaitingStepsId,
266
266
  );
267
267
 
268
268
  // ── Process graph objects ─────────────────────────────────────────────────
@@ -230,67 +230,77 @@ export default async function <%- functionName %>(
230
230
  );
231
231
  _izContext.logger.debug('awaitingStepId:', awaitingStepId);
232
232
 
233
- const recordAwaitingSteps = await dynamodbSharedLib.query(
233
+ const recordAwaitingStep = await asyncFlowSharedLib.findPendingStepAwaitingMultipleSteps(
234
234
  _izContext,
235
- await dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
236
- { awaitingStepId },
235
+ awaitingStepId
237
236
  );
238
- _izContext.logger.debug('record awaitingSteps::', recordAwaitingSteps);
239
-
240
- await Promise.all(
241
- recordAwaitingSteps.Items.map(async (recordAwaitingStep) => {
242
- if (
243
- await asyncFlowSharedLib.checkAllAwaitingStepsFinished(
244
- _izContext,
245
- recordAwaitingStep.pendingStepId,
246
- awaitingStepId,
247
- errorsFound,
248
- )
249
- ) {
250
- _izContext.logger.debug('finish all awaitingStep');
251
-
252
- await Promise.all(
253
- recordAwaitingStep.listOfRecords.map(async (listOfRecords) => {
254
- if (status === 'complete') {
255
- Object.entries(listOfRecords).map(async ([_, record]) => {
256
- await dynamodbSharedLib.putItem(
257
- _izContext,
258
- dynamodbSharedLib.tableName(_izContext,
259
- record.tableName,
260
- record.serviceTag
261
- ),
262
- {
263
- ...record.objInstanceFull.identifiers,
264
- ...record.objInstanceFull.fields
265
- }
266
- );
267
- })
268
- }
269
- }))
270
-
271
- const messageObject = _buildMessageObject(
272
- returnValue,
273
- existsNode,
274
- status,
275
- errorsFound,
276
- );
277
-
278
- //(<beforeSendMessage>)
279
- //(</beforeSendMessage>)
280
-
281
- await _sendSnsMessage(_izContext, passBackProperties, messageObject);
282
-
283
- //(<afterSendMessage>)
284
- //(</afterSendMessage>)
285
- }
286
-
287
- await asyncFlowSharedLib.removeAwaitingMultipleStep(
288
- _izContext,
289
- awaitingStepId,
290
- recordAwaitingStep.pendingStepId,
291
- errorsFound,
292
- );
293
- }),
237
+ _izContext.logger.debug('recordAwaitingStep:', recordAwaitingStep);
238
+
239
+ const {
240
+ isComplete,
241
+ collectedAttributes,
242
+ collectedErrors,
243
+ returnValues: _rvs,
244
+ additionalAttributes
245
+ } = await asyncFlowSharedLib.checkAllAwaitingStepsFinishedWithReturnParams(
246
+ _izContext,
247
+ recordAwaitingStep.pendingStepId,
248
+ awaitingStepId,
249
+ errorsFound,
250
+ );
251
+
252
+ _izContext.logger.debug('checkAllAwaitingStepsFinishedWithReturnParams:', {
253
+ isComplete,
254
+ collectedAttributes,
255
+ collectedErrors,
256
+ returnValues: _rvs,
257
+ additionalAttributes
258
+ });
259
+
260
+ if (isComplete) {
261
+ _izContext.logger.debug('finish all awaitingStep');
262
+
263
+ await Promise.all(
264
+ (recordAwaitingStep.listOfRecords ?? []).map(async (listOfRecords) => {
265
+ if (status === 'complete') {
266
+ Object.entries(listOfRecords).map(async ([_, record]) => {
267
+ await dynamodbSharedLib.putItem(
268
+ _izContext,
269
+ dynamodbSharedLib.tableName(_izContext,
270
+ record.tableName,
271
+ record.serviceTag
272
+ ),
273
+ {
274
+ ...record.objInstanceFull.identifiers,
275
+ ...record.objInstanceFull.fields
276
+ }
277
+ );
278
+ });
279
+ }
280
+ })
281
+ );
282
+
283
+ const messageObject = _buildMessageObject(
284
+ returnValue,
285
+ existsNode,
286
+ status,
287
+ errorsFound,
288
+ );
289
+
290
+ //(<beforeSendMessage>)
291
+ //(</beforeSendMessage>)
292
+
293
+ await _sendSnsMessage(_izContext, passBackProperties, messageObject);
294
+
295
+ //(<afterSendMessage>)
296
+ //(</afterSendMessage>)
297
+ }
298
+
299
+ await asyncFlowSharedLib.removeAwaitingMultipleStep(
300
+ _izContext,
301
+ awaitingStepId,
302
+ recordAwaitingStep.pendingStepId,
303
+ errorsFound,
294
304
  );
295
305
 
296
306
  //(<beforeReturn>)
@@ -171,8 +171,8 @@ export default async function <%- functionName %> (
171
171
  const matchToFrom = fromServiceTag === secondServiceTag && fromObjectType === secondObjectType &&
172
172
  toServiceTag === firstServiceTag && toObjType === firstObjectType;
173
173
 
174
- if ((newRelationshipSchema.relationshipDirection === "from" && matchFromTo) ||
175
- (newRelationshipSchema.relationshipDirection === "to" && matchToFrom)) {
174
+ if ((oldRelTypeAndDirection.relationshipDirection === "from" && matchFromTo) ||
175
+ (oldRelTypeAndDirection.relationshipDirection === "to" && matchToFrom)) {
176
176
  checkCorrectLink = true
177
177
  break;
178
178
  }
@@ -94,11 +94,19 @@ export const main = middleware.wrap(async (event, context) => {
94
94
 
95
95
  // add argument (to invoke lambda) to passOnProperties[]
96
96
  passOnProperties.push(record.body.Message);
97
+ <% if (functionName.endsWith('Complete')) { %>
98
+ passOnProperties.push(
99
+ callingFlowSharedLib.addPassBackPropertiesToPassOnProperties(
100
+ record.body.Message
101
+ )
102
+ );
103
+ <% } else { %>
97
104
  passOnProperties.push(
98
105
  callingFlowSharedLib.addCallingFlowToPassOnProperties(
99
106
  record.body.Message
100
107
  )
101
108
  );
109
+ <% } %>
102
110
  //(<additionalParams>)
103
111
  //(</additionalParams>)
104
112
  record._izContext.logger.debug(
@@ -76,11 +76,19 @@ export const main = middleware.wrap(async (event, context) => {
76
76
 
77
77
  // add argument (to invoke lambda) to passOnProperties[]
78
78
  passOnProperties.push(record.body.Message);
79
+ <% if (functionName.endsWith('Complete')) { %>
80
+ passOnProperties.push(
81
+ callingFlowSharedLib.addPassBackPropertiesToPassOnProperties(
82
+ record.body.Message
83
+ )
84
+ );
85
+ <% } else { %>
79
86
  passOnProperties.push(
80
87
  callingFlowSharedLib.addCallingFlowToPassOnProperties(
81
88
  record.body.Message
82
89
  )
83
90
  );
91
+ <% } %>
84
92
  //(<additionalParams>)
85
93
  //(</additionalParams>)
86
94
  record._izContext.logger.debug(
@@ -143,10 +143,8 @@ export async function generateFlowMainFunction(allSchemas, options) {
143
143
  // 2. Generate BeforeLogical Main Logic File if needed (CRUD flows only)
144
144
  const beforeLogicalConfigs = generateCode.filter(c => c.codeHookTag === 'beforeLogical');
145
145
  if (isCrud && beforeLogicalConfigs.length > 0) {
146
- const beforeLogicalTemplateStr = await fs.readFile(
147
- path.join(__dirname, '..', '..', 'FlowObjects', 'templates', 'FlowEndpointBeforeLogical_Main.ejs'),
148
- 'utf-8'
149
- );
146
+ const beforeLogicalTemplatePath = path.join(__dirname, '..', '..', 'FlowObjects', 'templates', 'step', 'FlowEndpointBeforeLogical_Main.ejs');
147
+ const beforeLogicalTemplateStr = await fs.readFile(beforeLogicalTemplatePath, 'utf-8');
150
148
 
151
149
  const beforeLogicalFileName = `${upperCaseFlowTag}BeforeLogical_Main`;
152
150
  const beforeLogicalContent = ejs.render(beforeLogicalTemplateStr, {
@@ -18,7 +18,7 @@ export async function generateFlowSteps(allSchemas, options) {
18
18
  // Read templates
19
19
  const templates = {
20
20
  main: await fs.readFile(path.join(__dirname, '..', '..', 'FlowSchemas', 'templates', 'step', 'FlowStep_Main.ejs'), 'utf-8'),
21
- complete: await fs.readFile(path.join(__dirname, '..', '..', 'FlowSchemas', 'templates', 'step', 'FlowEndpointComplete_Main.ejs'), 'utf-8'),
21
+ complete: await fs.readFile(path.join(__dirname, '..', '..', 'FlowObjects', 'templates', 'step', 'FlowEndpointComplete_Main.ejs'), 'utf-8'),
22
22
  sqs: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrSqs.ejs'), 'utf-8'),
23
23
  dsq: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrDsq.ejs'), 'utf-8'),
24
24
  api: await fs.readFile(path.join(__dirname, '..', 'handlers', 'FlowHandler_HdrApi.ejs'), 'utf-8'),
@@ -1,3 +1,5 @@
1
+ import { TYPE_MAP } from './propertyNormalizer.js';
2
+
1
3
  const ALLOWED_EVENTS = ['ownTopic', 'extTopic', 'lambdaSyncInv', 'lambdaSyncApi', 'eventBridge', 's3', 'dsq'];
2
4
  const MAIN_ASYNC_EVENTS = ['ownTopic', 'extTopic', 's3'];
3
5
  const RESERVED_SYSTEM_FLOW_TAGS = new Set([
@@ -48,6 +50,105 @@ function validateNamedRefs(flowTag, stepName, refs, defs, label, errors) {
48
50
  }
49
51
  }
50
52
 
53
+ function validateStepPropertyTypes(flowTag, propDefs, label, errors) {
54
+ if (!propDefs || typeof propDefs !== 'object') return;
55
+ const ALLOWED_TYPES = Object.keys(TYPE_MAP);
56
+ for (const [key, def] of Object.entries(propDefs)) {
57
+ if (!def || typeof def !== 'object') continue;
58
+ if (!def.type) {
59
+ errors.push(`Flow '${flowTag}' ${label} '${key}' is missing required field 'type'.`);
60
+ continue;
61
+ }
62
+ if (!ALLOWED_TYPES.includes(def.type)) {
63
+ errors.push(
64
+ `Flow '${flowTag}' ${label} '${key}' has unsupported type '${def.type}'. Allowed types are: ${ALLOWED_TYPES.join(', ')}.`
65
+ );
66
+ continue;
67
+ }
68
+ // enum: must supply enumValues array
69
+ if (def.type === 'enum') {
70
+ if (!Array.isArray(def.enumValues) || def.enumValues.length === 0) {
71
+ errors.push(
72
+ `Flow '${flowTag}' ${label} '${key}' has type 'enum' but is missing a non-empty 'enumValues' array.`
73
+ );
74
+ }
75
+ }
76
+ // default: must match resolved primitive type
77
+ // 'mixed' type accepts any value, so skip the type check
78
+ if (def.default !== undefined && def.type !== 'mixed') {
79
+ const resolvedPrimitive = TYPE_MAP[def.type];
80
+ const actualType = Array.isArray(def.default) ? 'array' : typeof def.default;
81
+ const expectedType = resolvedPrimitive === 'array' ? 'array' : resolvedPrimitive;
82
+ if (expectedType && actualType !== expectedType) {
83
+ errors.push(
84
+ `Flow '${flowTag}' ${label} '${key}' has a 'default' value of type '${actualType}' but expected '${expectedType}' (from schema type '${def.type}').`
85
+ );
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Warn about stepProperties / stepMessageAttributes keys that are declared
93
+ * but never referenced in any step's properties or messageAttributes arrays.
94
+ */
95
+ function validateUnusedStepProperties(flowTag, flow, errors) {
96
+ const stepProps = flow.stepProperties;
97
+ const stepMsgAttrs = flow.stepMessageAttributes;
98
+ if (!stepProps && !stepMsgAttrs) return;
99
+
100
+ const usedProps = new Set();
101
+ const usedAttrs = new Set();
102
+
103
+ for (const step of Array.isArray(flow.flowSteps) ? flow.flowSteps : []) {
104
+ for (const ref of Array.isArray(step.properties) ? step.properties : []) {
105
+ if (typeof ref === 'string') usedProps.add(ref);
106
+ }
107
+ for (const ref of Array.isArray(step.messageAttributes) ? step.messageAttributes : []) {
108
+ if (typeof ref === 'string') usedAttrs.add(ref);
109
+ }
110
+ }
111
+
112
+ if (stepProps && typeof stepProps === 'object') {
113
+ for (const key of Object.keys(stepProps)) {
114
+ if (!usedProps.has(key)) {
115
+ errors.push(
116
+ `Flow '${flowTag}' stepProperties key '${key}' is declared but never referenced in any step's 'properties'.`
117
+ );
118
+ }
119
+ }
120
+ }
121
+
122
+ if (stepMsgAttrs && typeof stepMsgAttrs === 'object') {
123
+ for (const key of Object.keys(stepMsgAttrs)) {
124
+ if (!usedAttrs.has(key)) {
125
+ errors.push(
126
+ `Flow '${flowTag}' stepMessageAttributes key '${key}' is declared but never referenced in any step's 'messageAttributes'.`
127
+ );
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Detect keys that appear in both stepProperties and stepMessageAttributes
135
+ * to prevent ambiguous naming.
136
+ */
137
+ function validateDuplicateStepPropertyKeys(flowTag, flow, errors) {
138
+ const stepProps = flow.stepProperties;
139
+ const stepMsgAttrs = flow.stepMessageAttributes;
140
+ if (!stepProps || !stepMsgAttrs) return;
141
+
142
+ const propKeys = new Set(Object.keys(stepProps));
143
+ for (const key of Object.keys(stepMsgAttrs)) {
144
+ if (propKeys.has(key)) {
145
+ errors.push(
146
+ `Flow '${flowTag}' key '${key}' is declared in both 'stepProperties' and 'stepMessageAttributes'. Keys must be unique across both groups.`
147
+ );
148
+ }
149
+ }
150
+ }
151
+
51
152
  export function validateFlowsForGeneration(flows) {
52
153
  const errors = [];
53
154
  const seenCustomFlowTags = new Set();
@@ -112,6 +213,11 @@ export function validateFlowsForGeneration(flows) {
112
213
  validateNamedRefs(flowTag, stepName, step.messageAttributes, flow.stepMessageAttributes, 'messageAttributes', errors);
113
214
  }
114
215
 
216
+ validateStepPropertyTypes(flowTag, flow.stepProperties, 'stepProperties', errors);
217
+ validateStepPropertyTypes(flowTag, flow.stepMessageAttributes, 'stepMessageAttributes', errors);
218
+ validateDuplicateStepPropertyKeys(flowTag, flow, errors);
219
+ validateUnusedStepProperties(flowTag, flow, errors);
220
+
115
221
  // ponytail: relaxed anyStepHasPlugInHooks requirement
116
222
  // since unified entry points (step 0) can now handle plugins directly without start.
117
223
 
@@ -1,20 +1,15 @@
1
- const TYPE_MAP = {
2
- string: 'string', number: 'number', integer: 'number',
3
- currency: 'string', currencyValue: 'number', numeric: 'number',
4
- float: 'number', special: 'string', object: 'object',
5
- boolean: 'boolean', array: 'array', arrayMixed: 'array',
6
- arrayObject: 'array', arrayNumeric: 'array', arrayString: 'array',
7
- timestamp: 'number'
8
- };
1
+ import { consts } from '@izara_project/izara-shared-service-schemas';
2
+
3
+ export const TYPE_MAP = consts.FIELDNAME_TYPE_MAPPING;
9
4
 
10
5
  export function normalizePropertyType(p, lookupSource) {
11
6
  let prop = typeof p === 'string'
12
7
  ? ((lookupSource && lookupSource[p]) ? lookupSource[p] : { propertyName: p, type: 'string' })
13
8
  : p;
14
-
9
+
15
10
  if (prop && prop.type) {
16
11
  prop = { ...prop, type: TYPE_MAP[prop.type] || prop.type };
17
12
  }
18
-
13
+
19
14
  return prop;
20
15
  }
@@ -44,6 +44,7 @@ export async function generateFlowOut(allSchemas, options) {
44
44
  'Create',
45
45
  'Update',
46
46
  'Delete',
47
+ 'Get',
47
48
  'CreateRelationship',
48
49
  'UpdateRelationship',
49
50
  'DeleteRelationship',
package/src/generate.js CHANGED
@@ -93,7 +93,7 @@ async function generate(rootPath, options = {}) {
93
93
 
94
94
  function patchFlowStepsValidationSchema() {
95
95
  const flowStepsSchema = basicValidatorSchema.basicFlowSchema?.properties?.flowSteps;
96
- if (!flowStepsSchema || flowStepsSchema.oneOf) {
96
+ if (!flowStepsSchema || flowStepsSchema.oneOf || !flowStepsSchema.items) {
97
97
  return;
98
98
  }
99
99
 
package/test_schema.js ADDED
@@ -0,0 +1,2 @@
1
+ import { basicValidatorSchema } from '@izara_project/izara-shared-service-schemas';
2
+ console.log(JSON.stringify(basicValidatorSchema.basicFlowSchema?.properties?.flowSteps, null, 2));