@izara_project/izara-core-generate-service-code 1.0.79 → 1.0.81

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 (27) hide show
  1. package/package.json +4 -4
  2. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +47 -3
  3. package/src/emit/crud/emitCrudApplicationArtifacts.js +12 -5
  4. package/src/emit/crud/templates/CreateComplete_Main.ejs +8 -3
  5. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +6 -1
  6. package/src/emit/crud/templates/CreateLogicalLib.ejs +6 -1
  7. package/src/emit/crud/templates/DeleteComplete_Main.ejs +8 -3
  8. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +7 -2
  9. package/src/emit/crud/templates/GetRangeEndpoint_Main.ejs +111 -37
  10. package/src/emit/crud/templates/UpdateComplete_Main.ejs +8 -3
  11. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +7 -2
  12. package/src/emit/crud/templates/UpdateLogicalLib.ejs +6 -1
  13. package/src/emit/relationship/templates/ChangeRelationshipComplete_Main.ejs +7 -2
  14. package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +5 -1
  15. package/src/emit/relationship/templates/CreateRelationshipComplete_Main.ejs +7 -2
  16. package/src/emit/relationship/templates/CreateRelationship_Main.ejs +5 -1
  17. package/src/emit/relationship/templates/DeleteRelationshipComplete_Main.ejs +7 -2
  18. package/src/emit/relationship/templates/DeleteRelationship_Main.ejs +5 -1
  19. package/src/emit/relationship/templates/MoveRelationshipComplete_Main.ejs +7 -2
  20. package/src/emit/relationship/templates/MoveRelationship_Main.ejs +5 -1
  21. package/src/emit/relationship/templates/UpdateRelationshipComplete_Main.ejs +7 -2
  22. package/src/emit/relationship/templates/UpdateRelationship_Main.ejs +5 -1
  23. package/src/emit/resource/emitResourceArtifacts.js +66 -11
  24. package/src/emit/resource/templates/DynamoDBTable_Yaml.ejs +4 -4
  25. package/test/crudApplicationArtifacts.test.js +11 -0
  26. package/test/customFlowApplicationArtifacts.test.js +59 -3
  27. package/test/resourceArtifacts.test.js +41 -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.79",
3
+ "version": "1.0.81",
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": {
@@ -26,13 +26,13 @@
26
26
  "@izara_project/izara-shared-core": "^1.0.13",
27
27
  "ejs": "^6.0.1",
28
28
  "js-beautify": "^2.0.3",
29
- "ws": "^8.21.2",
29
+ "ws": "^8.21.3",
30
30
  "yaml": "^2.9.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@izara_project/izara-core-library-logger": "^1.0.11",
34
- "@izara_project/izara-core-library-service-schemas": "1.0.161",
35
- "@izara_project/izara-shared-service-schemas": "^1.0.70",
34
+ "@izara_project/izara-core-library-service-schemas": "1.0.162",
35
+ "@izara_project/izara-shared-service-schemas": "^1.0.71",
36
36
  "jest": "^30.4.2",
37
37
  "prettier": "^3.9.6",
38
38
  "prettier-plugin-ejs": "^1.0.3"
@@ -146,13 +146,44 @@ const tableConfigs = {
146
146
  }
147
147
  });
148
148
 
149
+ const stepSubs = [];
150
+ const seenStepSubs = new Set();
151
+ (flows || []).forEach(function(flow) {
152
+ const flowTag = flow?.flowTag ? flow.flowTag[0].toUpperCase() + flow.flowTag.slice(1) : '';
153
+ (flow?.flowSteps || []).forEach(function(step) {
154
+ if (Array.isArray(step?.previousSteps)) {
155
+ const stepName = step?.stepName ? step.stepName[0].toUpperCase() + step.stepName.slice(1) : '';
156
+ const functionName = stepName.startsWith(flowTag) ? stepName : `${flowTag}${stepName}`;
157
+ const queueName = `${functionName}HdrSqs`;
158
+ step.previousSteps.forEach(function(prev) {
159
+ if (prev && prev.sourceType === 'extTopic' && prev.serviceTag && prev.topicName) {
160
+ const key = `${prev.serviceTag}:${prev.topicName}:${queueName}`;
161
+ if (!seenStepSubs.has(key)) {
162
+ seenStepSubs.add(key);
163
+ stepSubs.push({
164
+ serviceTag: prev.serviceTag,
165
+ topicName: prev.topicName,
166
+ sqsEndpoint: queueName
167
+ });
168
+ }
169
+ }
170
+ });
171
+ }
172
+ });
173
+ });
174
+
149
175
  const rbacSubs = [];
150
176
  if (crud && typeof rbacValidatorServiceTags !== 'undefined' && rbacValidatorServiceTags && rbacValidatorServiceTags.length > 0) {
151
177
  const allRbacTags = Array.from(new Set(['Rbac', ...rbacValidatorServiceTags]));
178
+ const hasGraph = typeof objectGraphServiceTags !== 'undefined' && objectGraphServiceTags && objectGraphServiceTags.length > 0;
179
+ const updateEndpoint = hasGraph ? 'UpdateRbacCallbackHdrSqs' : 'UpdateCompleteHdrSqs';
180
+ const deleteEndpoint = hasGraph ? 'DeleteRbacCallbackHdrSqs' : 'DeleteCompleteHdrSqs';
152
181
  allRbacTags.forEach(function(tag) {
153
- rbacSubs.push({ serviceTag: tag, topicName: 'ValidatorRbac_Out', sqsEndpoint: 'CreateCompleteHdrSqs' });
154
- rbacSubs.push({ serviceTag: tag, topicName: 'ValidatorRbac_Out', sqsEndpoint: 'UpdateCompleteHdrSqs' });
155
- rbacSubs.push({ serviceTag: tag, topicName: 'ValidatorRbac_Out', sqsEndpoint: 'DeleteCompleteHdrSqs' });
182
+ if (!hasGraph) {
183
+ rbacSubs.push({ serviceTag: tag, topicName: 'ValidatorRbac_Out', sqsEndpoint: 'CreateCompleteHdrSqs' });
184
+ }
185
+ rbacSubs.push({ serviceTag: tag, topicName: 'ValidatorRbac_Out', sqsEndpoint: updateEndpoint });
186
+ rbacSubs.push({ serviceTag: tag, topicName: 'ValidatorRbac_Out', sqsEndpoint: deleteEndpoint });
156
187
  });
157
188
  }
158
189
  -%>
@@ -195,6 +226,19 @@ const snsTopicSubscriptions = [
195
226
  },
196
227
  <% }); -%>
197
228
  <% } -%>
229
+ <% if (stepSubs.length > 0) { -%>
230
+
231
+ // ------------------------------------------------------------
232
+ // Step Subscriptions (from previousSteps)
233
+ // ------------------------------------------------------------
234
+ <% stepSubs.forEach(function(sub, idx) { -%>
235
+ {
236
+ serviceTag: '<%= sub.serviceTag %>',
237
+ topicName: '<%= sub.topicName %>',
238
+ sqsEndpoint: '<%= sub.sqsEndpoint %>'
239
+ },
240
+ <% }); -%>
241
+ <% } -%>
198
242
  <% if (rbacSubs.length > 0) { -%>
199
243
 
200
244
  // ------------------------------------------------------------
@@ -89,7 +89,8 @@ async function emitAction(action, objects, serviceTag) {
89
89
  startConfig: null,
90
90
  objects: action === 'Create' ? [] : objects,
91
91
  isCrudObject,
92
- hasRbacAddOn: (object) => hasRbacAddOn(object, objects)
92
+ hasRbacAddOn: (object) => hasRbacAddOn(object, objects),
93
+ plan
93
94
  };
94
95
  const artifacts = [
95
96
  {
@@ -276,13 +277,19 @@ async function emitAction(action, objects, serviceTag) {
276
277
 
277
278
  if (plan.hasComplete) {
278
279
  const completeMain = `${action}Complete_Main`;
280
+ const templateName =
281
+ plan.hasRbac && !plan.hasGraph
282
+ ? 'FlowEndpointRbacCallback_Main.ejs'
283
+ : `${action}Complete_Main.ejs`;
284
+ const templateContext =
285
+ plan.hasRbac && !plan.hasGraph
286
+ ? shared
287
+ : { action, ...shared };
288
+
279
289
  artifacts.push(
280
290
  {
281
291
  path: `${source}/${completeMain}.js`,
282
- content: await render(
283
- plan.hasRbac ? 'FlowEndpointRbacCallback_Main.ejs' : `${action}Complete_Main.ejs`,
284
- plan.hasRbac ? shared : { action }
285
- )
292
+ content: await render(templateName, templateContext)
286
293
  },
287
294
  {
288
295
  path: `${source}/${action}Complete_HdrSqs.js`,
@@ -118,8 +118,10 @@ export default async function CreateComplete(
118
118
  }
119
119
  : null;
120
120
  if (status === 'complete') {
121
+ const listOfRecords =
122
+ result?.additionalAttributes?.listOfRecords ?? [];
121
123
  await Promise.all(
122
- (pending.listOfRecords ?? []).flatMap((records) =>
124
+ (listOfRecords ?? []).flatMap((records) =>
123
125
  Object.values(records).map(async (record) =>
124
126
  dynamodbSharedLib.putItem(
125
127
  _izContext,
@@ -137,9 +139,12 @@ export default async function CreateComplete(
137
139
  )
138
140
  );
139
141
  }
142
+ const callingFlowConfig =
143
+ result?.additionalAttributes?.callingFlowConfig ||
144
+ passBackProperties;
140
145
  const message =
141
146
  callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
142
- passBackProperties,
147
+ callingFlowConfig,
143
148
  {
144
149
  objType: returnValue.requestParams.objType,
145
150
  objInstanceFull: node.objInstanceFull,
@@ -152,7 +157,7 @@ export default async function CreateComplete(
152
157
  );
153
158
  const messageAttributes =
154
159
  callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
155
- passBackProperties,
160
+ callingFlowConfig,
156
161
  {}
157
162
  );
158
163
  const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
@@ -375,7 +375,12 @@ export default async function <%- flowTag %>(
375
375
  _izContext.uniqueRequestId,
376
376
  'logical_<%= objectType %>'
377
377
  ),
378
- awaitingMultipleStepIds
378
+ awaitingMultipleStepIds,
379
+ {
380
+ additionalAttributes: {
381
+ callingFlowConfig
382
+ }
383
+ }
379
384
  );
380
385
 
381
386
  //(<beforePublish_<%= objectType %>>)
@@ -364,7 +364,12 @@ export async function <%- flowTag %>LogicalLib(
364
364
  _izContext.uniqueRequestId,
365
365
  PREFIX.CREATE_OBJECT_ASYNC_COMPLETE
366
366
  ),
367
- allAwaitingStepsId
367
+ allAwaitingStepsId,
368
+ {
369
+ additionalAttributes: {
370
+ callingFlowConfig
371
+ }
372
+ }
368
373
  );
369
374
  }
370
375
 
@@ -118,8 +118,10 @@ export default async function DeleteComplete(
118
118
  }
119
119
  : null;
120
120
  if (status === 'complete') {
121
+ const listOfRecords =
122
+ result?.additionalAttributes?.listOfRecords ?? [];
121
123
  await Promise.all(
122
- (pending.listOfRecords ?? []).flatMap((records) =>
124
+ (listOfRecords ?? []).flatMap((records) =>
123
125
  Object.values(records).map(async (record) =>
124
126
  dynamodbSharedLib.putItem(
125
127
  _izContext,
@@ -137,9 +139,12 @@ export default async function DeleteComplete(
137
139
  )
138
140
  );
139
141
  }
142
+ const callingFlowConfig =
143
+ result?.additionalAttributes?.callingFlowConfig ||
144
+ passBackProperties;
140
145
  const message =
141
146
  callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
142
- passBackProperties,
147
+ callingFlowConfig,
143
148
  {
144
149
  objType: returnValue.requestParams.objType,
145
150
  objInstanceFull: node.objInstanceFull,
@@ -152,7 +157,7 @@ export default async function DeleteComplete(
152
157
  );
153
158
  const messageAttributes =
154
159
  callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
155
- passBackProperties,
160
+ callingFlowConfig,
156
161
  {}
157
162
  );
158
163
  const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
@@ -141,7 +141,7 @@ export default async function <%- flowTag %>(
141
141
  serviceTag: <%- rbacServiceTags.length === 1 ? rbacServiceTags[0] : `{ ${Object.entries(rbacServiceTagMap).map(([k, v]) => `'${k}': ${v}`).join(', ')} }[requestParams.objectType] || process.env.iz_serviceTag` %>
142
142
  },
143
143
  requestParams: requestParams,
144
- nextFlowStepName: '<%- flowTag %>RbacCallback'
144
+ nextFlowStepName: '<%- plan?.hasRbacCallback ? `${flowTag}RbacCallback` : `${flowTag}Complete` %>'
145
145
  }
146
146
  );
147
147
 
@@ -250,7 +250,12 @@ export default async function <%- flowTag %>(
250
250
  _izContext.uniqueRequestId,
251
251
  'logical_<%= objectType %>'
252
252
  ),
253
- awaitingMultipleStepIds
253
+ awaitingMultipleStepIds,
254
+ {
255
+ additionalAttributes: {
256
+ callingFlowConfig
257
+ }
258
+ }
254
259
  );
255
260
 
256
261
  //(<beforePublish_<%= objectType %>>)
@@ -36,7 +36,7 @@ import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
36
36
  /**
37
37
  * Gets a range of objects by processing DynamoDB query
38
38
  * @param {Object} _izContext - Context object with logger
39
- * @param {Object} requestParams - { objectType, pk, sk, itemNum, additionalParams }
39
+ * @param {Object} requestParams - { objectType, identifierField, pk, startKey, sk, itemNumber, itemNum, additionalParams }
40
40
  * @param {Object} callingFlowConfig - Calling flow configuration
41
41
  * @returns {Promise<Object>} DynamoDB query result (Items, LastEvaluatedKey, etc.)
42
42
  */
@@ -63,22 +63,50 @@ export default async function <%- mainFileName %>(
63
63
  const getDataDetails = await createGetDataDetails(_izContext, objectSchema);
64
64
  _izContext.logger.debug('getDataDetails:', getDataDetails);
65
65
 
66
- const { pk, sk, itemNum } = requestParams;
67
-
68
- if (!pk) {
69
- throw new Error('Partition key (pk) is required for getRange');
66
+ const {
67
+ identifierField,
68
+ pk,
69
+ startKey,
70
+ sk,
71
+ itemNumber,
72
+ itemNum
73
+ } = requestParams;
74
+
75
+ let partitionKeyValue = {};
76
+ if (
77
+ identifierField &&
78
+ typeof identifierField === 'object' &&
79
+ Object.keys(identifierField).length > 0
80
+ ) {
81
+ partitionKeyValue = identifierField;
82
+ } else if (pk) {
83
+ const pkIdentifier = objectSchema.identifiers?.find(
84
+ (id) => id.type === 'partitionKey'
85
+ );
86
+ const pkName =
87
+ pkIdentifier?.fieldName ||
88
+ pkIdentifier?.name ||
89
+ 'pk';
90
+ partitionKeyValue = { [pkName]: pk };
91
+ } else {
92
+ throw new Error('Partition key (identifierField or pk) is required for getRange');
70
93
  }
71
94
 
72
95
  const queryElements = {};
73
- if (itemNum) {
74
- queryElements.limit = parseInt(itemNum, 10);
96
+ const limitVal = itemNumber || itemNum;
97
+ if (limitVal) {
98
+ queryElements.limit = parseInt(limitVal, 10);
75
99
  }
76
100
 
77
- if (sk) {
78
- if (typeof sk === 'object') {
79
- queryElements.exclusiveStartKey = sk;
101
+ const effectiveStartKey = startKey || sk;
102
+ if (effectiveStartKey) {
103
+ if (typeof effectiveStartKey === 'object') {
104
+ queryElements.exclusiveStartKey = effectiveStartKey;
80
105
  } else {
81
- queryElements.exclusiveStartKey = { pk: pk, sk: sk };
106
+ queryElements.exclusiveStartKey = {
107
+ ...partitionKeyValue,
108
+ sk: effectiveStartKey
109
+ };
82
110
  }
83
111
  }
84
112
 
@@ -86,17 +114,22 @@ export default async function <%- mainFileName %>(
86
114
  //(</beforeQuery>)
87
115
 
88
116
  // ── Process storage resources (DynamoDB only for getRange) ────────────────
89
- let queryResult = null;
90
-
91
- await Promise.all(
92
- Object.entries(getDataDetails).map(async ([, getDataDetail]) => {
93
- if (getDataDetail.storageType !== consts.STORAGE_TYPES.graph) {
94
- const tableName = await dynamodbSharedLib.tableName(
95
- _izContext,
96
- getDataDetail.tableName,
97
- getDataDetail.serviceTag
98
- );
99
-
117
+ const queryResults = [];
118
+
119
+ for (const [, getDataDetail] of Object.entries(getDataDetails)) {
120
+ if (getDataDetail.storageType !== consts.STORAGE_TYPES.graph) {
121
+ const tableName = await dynamodbSharedLib.tableName(
122
+ _izContext,
123
+ getDataDetail.tableName,
124
+ getDataDetail.serviceTag
125
+ );
126
+
127
+ let queryPartitionKeyValue = partitionKeyValue;
128
+ if (
129
+ !identifierField ||
130
+ typeof identifierField !== 'object' ||
131
+ Object.keys(identifierField).length === 0
132
+ ) {
100
133
  const pkIdentifier = objectSchema.identifiers?.find(
101
134
  (id) => id.type === 'partitionKey'
102
135
  );
@@ -105,26 +138,67 @@ export default async function <%- mainFileName %>(
105
138
  pkIdentifier?.fieldName ||
106
139
  pkIdentifier?.name ||
107
140
  'pk';
108
- const partitionKeyValue = { [pkName]: pk };
141
+ queryPartitionKeyValue = { [pkName]: pk };
142
+ }
109
143
 
110
- //(<beforeGetRangeDynamoDb>)
111
- //(</beforeGetRangeDynamoDb>)
144
+ //(<beforeGetRangeDynamoDb>)
145
+ //(</beforeGetRangeDynamoDb>)
112
146
 
113
- let dataFromDynamoDb = await dynamodbSharedLib.query(
114
- _izContext,
115
- tableName,
116
- partitionKeyValue,
117
- queryElements
118
- );
119
- _izContext.logger.debug('dataFromDynamoDb:', dataFromDynamoDb);
147
+ const dataFromDynamoDb = await dynamodbSharedLib.query(
148
+ _izContext,
149
+ tableName,
150
+ queryPartitionKeyValue,
151
+ queryElements
152
+ );
153
+ _izContext.logger.debug('dataFromDynamoDb:', dataFromDynamoDb);
154
+
155
+ queryResults.push(dataFromDynamoDb);
120
156
 
121
- queryResult = dataFromDynamoDb;
157
+ //(<afterGetRangeDynamoDb>)
158
+ //(</afterGetRangeDynamoDb>)
159
+ }
160
+ }
161
+
162
+ let queryResult = null;
163
+ if (queryResults.length === 1) {
164
+ queryResult = queryResults[0];
165
+ } else if (queryResults.length > 1) {
166
+ const mergedItemsMap = new Map();
167
+ const sortKeyIdentifier = objectSchema.identifiers?.find(
168
+ (id) => id.type === 'sortKey'
169
+ );
170
+ const skField =
171
+ sortKeyIdentifier?.fieldName ||
172
+ sortKeyIdentifier?.name ||
173
+ 'sk';
174
+
175
+ for (const res of queryResults) {
176
+ let items = [];
177
+ if (Array.isArray(res)) {
178
+ items = res;
179
+ } else if (res && res.Items) {
180
+ items = res.Items;
181
+ }
122
182
 
123
- //(<afterGetRangeDynamoDb>)
124
- //(</afterGetRangeDynamoDb>)
183
+ for (const item of items) {
184
+ let key = item[skField];
185
+ if (typeof key === 'undefined' || key === null) {
186
+ key = JSON.stringify(item);
187
+ }
188
+ if (mergedItemsMap.has(key)) {
189
+ mergedItemsMap.set(key, { ...mergedItemsMap.get(key), ...item });
190
+ } else {
191
+ mergedItemsMap.set(key, { ...item });
192
+ }
125
193
  }
126
- })
127
- );
194
+ }
195
+
196
+ queryResult = {
197
+ Items: Array.from(mergedItemsMap.values()),
198
+ Count: mergedItemsMap.size,
199
+ queryResults: queryResults
200
+ };
201
+ }
128
202
 
129
203
  return queryResult;
130
204
  } catch (err) {
@@ -118,8 +118,10 @@ export default async function UpdateComplete(
118
118
  }
119
119
  : null;
120
120
  if (status === 'complete') {
121
+ const listOfRecords =
122
+ result?.additionalAttributes?.listOfRecords ?? [];
121
123
  await Promise.all(
122
- (pending.listOfRecords ?? []).flatMap((records) =>
124
+ (listOfRecords ?? []).flatMap((records) =>
123
125
  Object.values(records).map(async (record) =>
124
126
  dynamodbSharedLib.putItem(
125
127
  _izContext,
@@ -137,9 +139,12 @@ export default async function UpdateComplete(
137
139
  )
138
140
  );
139
141
  }
142
+ const callingFlowConfig =
143
+ result?.additionalAttributes?.callingFlowConfig ||
144
+ passBackProperties;
140
145
  const message =
141
146
  callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
142
- passBackProperties,
147
+ callingFlowConfig,
143
148
  {
144
149
  objType: returnValue.requestParams.objType,
145
150
  objInstanceFull: node.objInstanceFull,
@@ -152,7 +157,7 @@ export default async function UpdateComplete(
152
157
  );
153
158
  const messageAttributes =
154
159
  callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
155
- passBackProperties,
160
+ callingFlowConfig,
156
161
  {}
157
162
  );
158
163
  const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
@@ -137,7 +137,7 @@ export default async function <%- flowTag %>(
137
137
  serviceTag: <%- rbacServiceTags.length === 1 ? rbacServiceTags[0] : `{ ${Object.entries(rbacServiceTagMap).map(([k, v]) => `'${k}': ${v}`).join(', ')} }[requestParams.objectType] || process.env.iz_serviceTag` %>
138
138
  },
139
139
  requestParams: requestParams,
140
- nextFlowStepName: '<%- flowTag %>RbacCallback'
140
+ nextFlowStepName: '<%- plan?.hasRbacCallback ? `${flowTag}RbacCallback` : `${flowTag}Complete` %>'
141
141
  }
142
142
  );
143
143
 
@@ -246,7 +246,12 @@ export default async function <%- flowTag %>(
246
246
  _izContext.uniqueRequestId,
247
247
  'logical_<%= objectType %>'
248
248
  ),
249
- awaitingMultipleStepIds
249
+ awaitingMultipleStepIds,
250
+ {
251
+ additionalAttributes: {
252
+ callingFlowConfig
253
+ }
254
+ }
250
255
  );
251
256
 
252
257
  //(<beforePublish_<%= objectType %>>)
@@ -287,7 +287,12 @@ export async function <%- flowTag %>LogicalLib(
287
287
  asyncFlowSharedLib.createPendingStepId(
288
288
  _izContext.uniqueRequestId
289
289
  ),
290
- awaitingStepIds
290
+ awaitingStepIds,
291
+ {
292
+ additionalAttributes: {
293
+ callingFlowConfig
294
+ }
295
+ }
291
296
  );
292
297
  _izContext.logger.debug(
293
298
  'awaitingMultipleStep',
@@ -110,9 +110,14 @@ export default async function changeRelationshipComplete(
110
110
  errorsFound: errorsFound
111
111
  };
112
112
 
113
+ const callingFlowConfig =
114
+ pending?.additionalAttributes?.callingFlowConfig ||
115
+ result?.additionalAttributes?.callingFlowConfig ||
116
+ passBackProperties;
117
+
113
118
  changeRelCompleteMsg =
114
119
  callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
115
- passBackProperties,
120
+ callingFlowConfig,
116
121
  changeRelCompleteMsg
117
122
  );
118
123
  _izContext.logger.debug(
@@ -122,7 +127,7 @@ export default async function changeRelationshipComplete(
122
127
 
123
128
  let messageAttributes =
124
129
  callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
125
- passBackProperties,
130
+ callingFlowConfig,
126
131
  {}
127
132
  );
128
133
  _izContext.logger.debug(
@@ -421,7 +421,11 @@ export default async function <%- functionName %>(
421
421
  PREFIX.changeRel // prefix, use constant further
422
422
  ),
423
423
  awaitingStepIds,
424
- {}
424
+ {
425
+ additionalAttributes: {
426
+ callingFlowConfig
427
+ }
428
+ }
425
429
  );
426
430
 
427
431
  // prepare message body
@@ -109,9 +109,14 @@ export default async function createRelationshipComplete(
109
109
  errorsFound: errorsFound
110
110
  };
111
111
 
112
+ const callingFlowConfig =
113
+ pending?.additionalAttributes?.callingFlowConfig ||
114
+ result?.additionalAttributes?.callingFlowConfig ||
115
+ passBackProperties;
116
+
112
117
  createCompleteMsg =
113
118
  callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
114
- passBackProperties,
119
+ callingFlowConfig,
115
120
  createCompleteMsg
116
121
  );
117
122
  _izContext.logger.debug(
@@ -121,7 +126,7 @@ export default async function createRelationshipComplete(
121
126
 
122
127
  let messageAttributes =
123
128
  callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
124
- passBackProperties,
129
+ callingFlowConfig,
125
130
  {}
126
131
  );
127
132
  _izContext.logger.debug(
@@ -449,7 +449,11 @@ export default async function createRelationship(
449
449
  PREFIX.createRel // prefix, use constant further
450
450
  ),
451
451
  awaitingStepIds,
452
- {}
452
+ {
453
+ additionalAttributes: {
454
+ callingFlowConfig
455
+ }
456
+ }
453
457
  );
454
458
 
455
459
  // prepare message body
@@ -107,9 +107,14 @@ export default async function deleteRelationshipComplete(
107
107
  errorsFound: errorsFound
108
108
  };
109
109
 
110
+ const callingFlowConfig =
111
+ pending?.additionalAttributes?.callingFlowConfig ||
112
+ result?.additionalAttributes?.callingFlowConfig ||
113
+ passBackProperties;
114
+
110
115
  deleteRelComp =
111
116
  callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
112
- passBackProperties,
117
+ callingFlowConfig,
113
118
  deleteRelComp
114
119
  );
115
120
  _izContext.logger.debug(
@@ -119,7 +124,7 @@ export default async function deleteRelationshipComplete(
119
124
 
120
125
  let messageAttributes =
121
126
  callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
122
- passBackProperties,
127
+ callingFlowConfig,
123
128
  {}
124
129
  );
125
130
  _izContext.logger.debug(
@@ -332,7 +332,11 @@ export default async function deleteRelationship(
332
332
  PREFIX.deleteRel // prefix, use constant further
333
333
  ),
334
334
  awaitingStepIds,
335
- {}
335
+ {
336
+ additionalAttributes: {
337
+ callingFlowConfig
338
+ }
339
+ }
336
340
  );
337
341
 
338
342
  // prepare message body
@@ -112,9 +112,14 @@ export default async function <%- functionName %>(
112
112
  errorsFound: errorsFound
113
113
  };
114
114
 
115
+ const callingFlowConfig =
116
+ pending?.additionalAttributes?.callingFlowConfig ||
117
+ result?.additionalAttributes?.callingFlowConfig ||
118
+ passBackProperties;
119
+
115
120
  moveRelCompleteMsg =
116
121
  callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
117
- passBackProperties,
122
+ callingFlowConfig,
118
123
  moveRelCompleteMsg
119
124
  );
120
125
  _izContext.logger.debug(
@@ -124,7 +129,7 @@ export default async function <%- functionName %>(
124
129
 
125
130
  let messageAttributes =
126
131
  callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
127
- passBackProperties,
132
+ callingFlowConfig,
128
133
  {}
129
134
  );
130
135
  _izContext.logger.debug(
@@ -413,7 +413,11 @@ export default async function moveRelationship(
413
413
  PREFIX.changeRel // prefix, use constant further
414
414
  ),
415
415
  awaitingStepIds,
416
- {}
416
+ {
417
+ additionalAttributes: {
418
+ callingFlowConfig
419
+ }
420
+ }
417
421
  );
418
422
 
419
423
  // prepare message body
@@ -109,9 +109,14 @@ export default async function updateRelationshipComplete(
109
109
  //(</inUpdateCompleteMsg>)
110
110
  };
111
111
 
112
+ const callingFlowConfig =
113
+ pending?.additionalAttributes?.callingFlowConfig ||
114
+ result?.additionalAttributes?.callingFlowConfig ||
115
+ passBackProperties;
116
+
112
117
  updateCompleteMsg =
113
118
  callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
114
- passBackProperties,
119
+ callingFlowConfig,
115
120
  updateCompleteMsg
116
121
  );
117
122
  _izContext.logger.debug(
@@ -121,7 +126,7 @@ export default async function updateRelationshipComplete(
121
126
 
122
127
  let messageAttributes =
123
128
  callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
124
- passBackProperties,
129
+ callingFlowConfig,
125
130
  {}
126
131
  );
127
132
  _izContext.logger.debug(
@@ -407,7 +407,11 @@ export default async function updateRelationship(
407
407
  PREFIX.updateRel // prefix, use constant further
408
408
  ),
409
409
  awaitingStepIds,
410
- {}
410
+ {
411
+ additionalAttributes: {
412
+ callingFlowConfig
413
+ }
414
+ }
411
415
  );
412
416
 
413
417
  // prepare message body
@@ -808,17 +808,18 @@ function collectTables(schemas) {
808
808
  const identifiers = Array.isArray(schema.identifiers)
809
809
  ? schema.identifiers
810
810
  : [];
811
- const pk =
812
- keyName(
813
- identifiers.find(
814
- (identifier) =>
815
- identifier.type === 'partitionKey' ||
816
- identifier.type === 'identifier'
817
- )
818
- ) ?? 'PK';
819
- const sk = keyName(
820
- identifiers.find((identifier) => identifier.type === 'sortKey')
811
+ const pkIdentifier = identifiers.find(
812
+ (identifier) =>
813
+ identifier.type === 'partitionKey' ||
814
+ identifier.type === 'identifier'
821
815
  );
816
+ const skIdentifier = identifiers.find(
817
+ (identifier) => identifier.type === 'sortKey'
818
+ );
819
+ const pk = keyName(pkIdentifier) ?? 'PK';
820
+ const sk = keyName(skIdentifier);
821
+ const pkType = keyAttributeType(pkIdentifier, schema);
822
+ const skType = keyAttributeType(skIdentifier, schema);
822
823
  for (const resource of Object.values(schema.storageResources)) {
823
824
  if (
824
825
  (resource?.storageType === 'dynamo' ||
@@ -827,12 +828,66 @@ function collectTables(schemas) {
827
828
  resource.tableName &&
828
829
  !tables.has(resource.tableName)
829
830
  )
830
- tables.set(resource.tableName, { pk, sk });
831
+ tables.set(resource.tableName, { pk, sk, pkType, skType });
831
832
  }
832
833
  }
833
834
  return tables;
834
835
  }
835
836
 
837
+ function keyAttributeType(identifier, schema) {
838
+ if (!identifier) return 'S';
839
+ if (identifier.attributeType) {
840
+ return identifier.attributeType;
841
+ }
842
+ if (identifier.dataType) {
843
+ return mapTypeToAttributeType(identifier.dataType);
844
+ }
845
+ if (identifier.fieldType) {
846
+ return mapTypeToAttributeType(identifier.fieldType);
847
+ }
848
+ const fieldName = identifier.fieldName ?? identifier.name;
849
+ if (fieldName && schema) {
850
+ let fieldDef =
851
+ schema.fieldNames?.[fieldName] ?? schema.properties?.[fieldName];
852
+ if (!fieldDef && Array.isArray(schema.addOnDataStructure)) {
853
+ for (const addOn of schema.addOnDataStructure) {
854
+ if (addOn?.fieldNames?.[fieldName]) {
855
+ fieldDef = addOn.fieldNames[fieldName];
856
+ break;
857
+ }
858
+ }
859
+ }
860
+ if (fieldDef?.attributeType) {
861
+ return fieldDef.attributeType;
862
+ }
863
+ const type = fieldDef?.type ?? fieldDef?.dataType;
864
+ if (type) {
865
+ return mapTypeToAttributeType(type);
866
+ }
867
+ }
868
+ return 'S';
869
+ }
870
+
871
+ function mapTypeToAttributeType(type) {
872
+ if (typeof type !== 'string') return 'S';
873
+ const lower = type.toLowerCase();
874
+ if (
875
+ lower === 'number' ||
876
+ lower === 'numeric' ||
877
+ lower === 'integer' ||
878
+ lower === 'float' ||
879
+ lower === 'int' ||
880
+ lower === 'double' ||
881
+ lower === 'n'
882
+ ) {
883
+ return 'N';
884
+ }
885
+ if (lower === 'binary' || lower === 'b') {
886
+ return 'B';
887
+ }
888
+ return 'S';
889
+ }
890
+
836
891
  function keyName(identifier) {
837
892
  if (!identifier) return null;
838
893
  return (
@@ -6,10 +6,10 @@
6
6
  BillingMode: PAY_PER_REQUEST
7
7
  AttributeDefinitions:
8
8
  - AttributeName: '<%- pk %>'
9
- AttributeType: 'S'
9
+ AttributeType: '<%- pkType || 'S' %>'
10
10
  <% if (sk) { %>
11
11
  - AttributeName: '<%- sk %>'
12
- AttributeType: 'S'
12
+ AttributeType: '<%- skType || 'S' %>'
13
13
  <% } %>
14
14
  KeySchema:
15
15
  - AttributeName: '<%- pk %>'
@@ -18,6 +18,6 @@
18
18
  - AttributeName: '<%- sk %>'
19
19
  KeyType: 'RANGE'
20
20
  <% } %>
21
- #<#<%- tableName %>TableSetting#>
22
- #<#/<%- tableName %>TableSetting#>
21
+ #<#<%- tableName %>TableSetting#>
22
+ #<#/<%- tableName %>TableSetting#>
23
23
 
@@ -282,6 +282,17 @@ test('emits GetRangeHdrApi when an object defines a sortKey identifier', async (
282
282
  );
283
283
  assert.match(yaml, /GetRangeHdrApi:/);
284
284
  assert.match(yaml, /path: \/orders\/order\/getRange/);
285
+ const getRangeMain = await readFile(
286
+ join(
287
+ rootPath,
288
+ 'app/src/generatedCode/FlowObjects/Get/source/GetRange_Main.js'
289
+ ),
290
+ 'utf8'
291
+ );
292
+ assert.match(getRangeMain, /identifierField/);
293
+ assert.match(getRangeMain, /startKey/);
294
+ assert.match(getRangeMain, /itemNumber/);
295
+ assert.match(getRangeMain, /queryResults/);
285
296
  });
286
297
 
287
298
  test('removes managed CRUD artifacts when no applicable object remains', async () => {
@@ -914,9 +914,8 @@ test('renders RBAC Validator subscriptions in InitialSetupConfig when object has
914
914
  );
915
915
  assert.match(initialSetupConfigContent, /serviceTag: 'UserAccount'/);
916
916
  assert.match(initialSetupConfigContent, /topicName: 'ValidatorRbac_Out'/);
917
- assert.match(initialSetupConfigContent, /sqsEndpoint: 'CreateCompleteHdrSqs'/);
918
- assert.match(initialSetupConfigContent, /sqsEndpoint: 'UpdateCompleteHdrSqs'/);
919
- assert.match(initialSetupConfigContent, /sqsEndpoint: 'DeleteCompleteHdrSqs'/);
917
+ assert.match(initialSetupConfigContent, /sqsEndpoint: 'UpdateRbacCallbackHdrSqs'/);
918
+ assert.match(initialSetupConfigContent, /sqsEndpoint: 'DeleteRbacCallbackHdrSqs'/);
920
919
  });
921
920
 
922
921
  test('deduplicates duplicate nextSteps topics across flowSteps in InitialSetupConfig', async () => {
@@ -1111,3 +1110,60 @@ test('renders multi-type array for mixed property and messageAttribute type in h
1111
1110
  /Value:\s*\{\s*type:\s*\['string',\s*'number',\s*'integer',\s*'boolean',\s*'null',\s*'object',\s*'array'\]/
1112
1111
  );
1113
1112
  });
1113
+
1114
+ test('renders previousSteps extTopic subscriptions in InitialSetupConfig', async () => {
1115
+ const rootPath = await mkdtemp(
1116
+ join(tmpdir(), 'generate-service-code-v2-prevsteps-')
1117
+ );
1118
+ roots.add(rootPath);
1119
+ const schemas = join(rootPath, 'app/src/schemas/FlowSchemas');
1120
+ await mkdir(schemas, { recursive: true });
1121
+ await writeFile(
1122
+ join(schemas, 'Order.js'),
1123
+ `export default [
1124
+ {
1125
+ flowTag: 'order',
1126
+ event: ['ownTopic'],
1127
+ flowSteps: [
1128
+ {
1129
+ stepName: 'Start'
1130
+ },
1131
+ {
1132
+ stepName: 'BuildOrderSellOfferLinkAndReserveOrderData',
1133
+ properties: ['returnValue', 'parentFlowId', 'status'],
1134
+ messageAttributes: [],
1135
+ event: ['extTopic'],
1136
+ previousSteps: [
1137
+ {
1138
+ sourceType: 'extTopic',
1139
+ serviceTag: 'GraphHandler',
1140
+ topicName: 'CreateNode_Out',
1141
+ fromStepNames: ['start']
1142
+ }
1143
+ ]
1144
+ }
1145
+ ]
1146
+ }
1147
+ ];\n`
1148
+ );
1149
+
1150
+ const result = await generate(rootPath, {
1151
+ serviceTag: 'Order'
1152
+ });
1153
+ assert.ok(result);
1154
+
1155
+ const initialSetupConfigContent = await readFile(
1156
+ join(
1157
+ rootPath,
1158
+ 'app/src/generatedCode/CoreFunctions/InitialSetupConfig.js'
1159
+ ),
1160
+ 'utf8'
1161
+ );
1162
+
1163
+ assert.match(initialSetupConfigContent, /serviceTag: 'GraphHandler'/);
1164
+ assert.match(initialSetupConfigContent, /topicName: 'CreateNode_Out'/);
1165
+ assert.match(
1166
+ initialSetupConfigContent,
1167
+ /sqsEndpoint: 'OrderBuildOrderSellOfferLinkAndReserveOrderDataHdrSqs'/
1168
+ );
1169
+ });
@@ -244,6 +244,47 @@ test('emits DynamoDB tables and hooks defined in flowSchemas storageResources',
244
244
  assert.match(dynamoYaml, /#<#\/FlowCustomTableTableSetting#>/);
245
245
  });
246
246
 
247
+ test('emits DynamoDB table with number sortKey attribute type when field type is number', async () => {
248
+ const rootPath = await createProject();
249
+ await writeFile(
250
+ join(rootPath, 'app/src/schemas/ObjectSchemas/CartOrder.js'),
251
+ "export default { " +
252
+ "objectType: 'cartOrder', " +
253
+ "generatedBy: 'userGenerated', " +
254
+ "fieldNames: { " +
255
+ " cartId: { type: 'string' }, " +
256
+ " sortId: { type: 'number' } " +
257
+ "}, " +
258
+ "identifiers: [ " +
259
+ " { type: 'partitionKey', fieldName: 'cartId' }, " +
260
+ " { type: 'sortKey', fieldName: 'sortId' } " +
261
+ "], " +
262
+ "storageResources: { " +
263
+ " main: { storageType: 'dynamoDB', tableName: 'CartOrders' } " +
264
+ "} " +
265
+ "};\n"
266
+ );
267
+
268
+ await generateResourceArtifacts(rootPath);
269
+
270
+ const dynamoYaml = await readFile(
271
+ join(
272
+ rootPath,
273
+ 'resource/sls_yaml/generatedCode/source/generated-dynamoDB-table.yml'
274
+ ),
275
+ 'utf8'
276
+ );
277
+ assert.match(dynamoYaml, /CartOrdersTable:/);
278
+ assert.match(
279
+ dynamoYaml,
280
+ /AttributeName:\s*'cartId'[\s\S]*?AttributeType:\s*'S'/
281
+ );
282
+ assert.match(
283
+ dynamoYaml,
284
+ /AttributeName:\s*'sortId'[\s\S]*?AttributeType:\s*'N'/
285
+ );
286
+ });
287
+
247
288
  test('emits IAM role for generated DSQ flows in generate-shared-resource.yml', async () => {
248
289
  const rootPath = await createProject();
249
290
  const schemas = join(rootPath, 'app/src/schemas');