@izara_project/izara-core-generate-service-code 1.0.88 → 1.0.90

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 (29) hide show
  1. package/package.json +1 -1
  2. package/src/compile/flowValidator.js +25 -0
  3. package/src/compile/loadSchemas.js +154 -0
  4. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +3 -7
  5. package/src/emit/crud/crudActionPlan.js +67 -9
  6. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +15 -32
  7. package/src/emit/crud/templates/CreateComplete_Main.ejs +135 -10
  8. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +33 -27
  9. package/src/emit/crud/templates/CreateLogicalLib.ejs +282 -95
  10. package/src/emit/crud/templates/CreateNodeCallback_Main.ejs +38 -21
  11. package/src/emit/crud/templates/DeleteAfterHookCallback_Main.ejs +118 -0
  12. package/src/emit/crud/templates/DeleteComplete_Main.ejs +124 -10
  13. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +26 -16
  14. package/src/emit/crud/templates/DeleteLogicalLib.ejs +113 -4
  15. package/src/emit/crud/templates/UpdateAfterHookCallback_Main.ejs +120 -0
  16. package/src/emit/crud/templates/UpdateComplete_Main.ejs +104 -10
  17. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +34 -22
  18. package/src/emit/crud/templates/UpdateLogicalLib.ejs +121 -4
  19. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +8 -2
  20. package/src/emit/rbac/templates/CancelUsage_Main.ejs +34 -14
  21. package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +34 -14
  22. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +20 -13
  23. package/src/emit/resource/emitResourceArtifacts.js +55 -14
  24. package/test/createTargetRoleHandler.test.js +11 -8
  25. package/test/crudApplicationArtifacts.test.js +440 -0
  26. package/test/dynamicCrudFlowSchemas.test.js +10 -5
  27. package/test/dynamicRbacFlowSchemas.test.js +24 -2
  28. package/test/graphDrivenFlowSchema.test.js +36 -0
  29. package/test/resourceArtifacts.test.js +113 -0
@@ -172,7 +172,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
172
172
  _setL1Cache(fullCacheKey, result);
173
173
  await _setL2Cache(_izContext, submittedByUserId, cacheSubKey, result);
174
174
  await _publicMessageOut(_izContext, { ...result, parentFlowId });
175
- return 'Completed';
175
+ return result;
176
176
  }
177
177
 
178
178
  // 1. Check L1 In-Memory Cache
@@ -180,7 +180,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
180
180
  if (l1Hit) {
181
181
  _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] L1 Cache hit:`, l1Hit);
182
182
  await _publicMessageOut(_izContext, { ...l1Hit, parentFlowId });
183
- return;
183
+ return l1Hit;
184
184
  }
185
185
 
186
186
  // 2. Check L2 Shared DynamoDB Cache
@@ -189,7 +189,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
189
189
  _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] L2 Cache hit:`, l2Hit);
190
190
  _setL1Cache(fullCacheKey, l2Hit);
191
191
  await _publicMessageOut(_izContext, { ...l2Hit, parentFlowId });
192
- return;
192
+ return l2Hit;
193
193
  }
194
194
 
195
195
  _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] Cache miss — querying DynamoDB`);
@@ -237,7 +237,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
237
237
  _setL1Cache(fullCacheKey, result);
238
238
  await _setL2Cache(_izContext, submittedByUserId, cacheSubKey, result);
239
239
  await _publicMessageOut(_izContext, { ...result, parentFlowId });
240
- return;
240
+ return result;
241
241
  }
242
242
 
243
243
  const rolePermissionsTable = dynamodbSharedLib.tableName(
@@ -289,19 +289,26 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
289
289
 
290
290
  await _publicMessageOut(_izContext, { ...cacheResult, parentFlowId });
291
291
 
292
- return 'Completed';
292
+ return cacheResult;
293
293
  }
294
294
 
295
295
  async function _publicMessageOut(_izContext, message) {
296
- const topicOut = snsSharedLib.snsTopicArnByFlowSchema(
297
- _izContext,
298
- process.env.iz_flowTag + '_Out'
299
- );
296
+ try {
297
+ const topicOut = snsSharedLib.snsTopicArnByFlowSchema(
298
+ _izContext,
299
+ process.env.iz_flowTag + '_Out'
300
+ );
300
301
 
301
- await sns.publishAsync(_izContext, {
302
- TopicArn: topicOut,
303
- Message: JSON.stringify(message)
304
- });
302
+ await sns.publishAsync(_izContext, {
303
+ TopicArn: topicOut,
304
+ Message: JSON.stringify(message)
305
+ });
306
+ } catch (error) {
307
+ _izContext.logger.warn(
308
+ `[${process.env.iz_serviceTag}:_publicMessageOut] Failed to publish message out:`,
309
+ error
310
+ );
311
+ }
305
312
  }
306
313
 
307
314
  function _matchesParameterGroups(groups, input) {
@@ -204,7 +204,7 @@ export const resourceArtifactPaths = [
204
204
  function createResourceState({ objects, relationships, flows }) {
205
205
  const crud = objects.some(isCrudObject);
206
206
  const selfRbac = objects.some(hasSelfRbacAddOn);
207
- const rbac = objects.some(hasSelfRbacAddOn);
207
+ const rbac = objects.some((obj) => hasRbacAddOn(obj, objects));
208
208
  const crudActionPlans = createCrudActionPlans(objects, flows);
209
209
 
210
210
  const crudOwnTopics = crud ? [...CRUD_ACTIONS] : [];
@@ -244,9 +244,9 @@ function createResourceState({ objects, relationships, flows }) {
244
244
  rbacSqsQueues.push(`Process${action}HdrSqs`);
245
245
  }
246
246
  }
247
- if (rbac) {
247
+ if (selfRbac) {
248
248
  const flowTags = [
249
- ...new Set(objects.filter(hasRbacAddOn).map(rbacFlowTag))
249
+ ...new Set(objects.filter(hasSelfRbacAddOn).map(rbacFlowTag))
250
250
  ];
251
251
  for (const flowTag of flowTags) {
252
252
  rbacOwnTopics.push(flowTag);
@@ -433,6 +433,7 @@ function createResourceState({ objects, relationships, flows }) {
433
433
  return {
434
434
  crud,
435
435
  rbac,
436
+ selfRbac,
436
437
  crudActionPlans,
437
438
  crudOwnTopics: [...new Set(crudOwnTopics)].filter(Boolean).sort(),
438
439
  crudSqsQueues: [...new Set(crudSqsQueues)].filter(Boolean).sort(),
@@ -638,7 +639,7 @@ async function renderDynamo(state) {
638
639
  const [system, ...tables] = await Promise.all([
639
640
  render('SystemDynamoDB_Yaml.ejs', {
640
641
  hasS3: state.s3FlowTags.length > 0,
641
- hasRbac: state.rbac
642
+ hasRbac: state.selfRbac
642
643
  }),
643
644
  ...state.tables.map((table) => render('DynamoDBTable_Yaml.ejs', table))
644
645
  ]);
@@ -650,7 +651,7 @@ async function renderIam(state) {
650
651
  const roles = ['ProcFindDataRole', 'RegisterRole'];
651
652
  if (state.crud) roles.push('FlowObjectsRole');
652
653
  if (state.relationships) roles.push('FlowRelationshipRole');
653
- if (state.rbac) roles.push('FlowRbacRole');
654
+ if (state.selfRbac) roles.push('FlowRbacRole');
654
655
  if (state.websocket) roles.push('WebSocketMainRole');
655
656
  roles.push(...state.customRoles);
656
657
  const sortedRoles = roles.sort();
@@ -668,8 +669,21 @@ async function renderIam(state) {
668
669
  function getRbacValidatorServiceTags(state) {
669
670
  const tags = new Set();
670
671
  for (const obj of state.objects || []) {
671
- if (obj.belongTo?.serviceTag && obj.belongTo.serviceTag !== 'Rbac') {
672
- tags.add(obj.belongTo.serviceTag);
672
+ const targets = obj?.belongTo
673
+ ? Array.isArray(obj.belongTo)
674
+ ? obj.belongTo
675
+ : [obj.belongTo]
676
+ : [];
677
+ for (const target of targets) {
678
+ if (target?.serviceTag && target.serviceTag !== 'Rbac') {
679
+ tags.add(target.serviceTag);
680
+ }
681
+ }
682
+ if (
683
+ obj.belongToSchema?.serviceTag &&
684
+ obj.belongToSchema.serviceTag !== 'Rbac'
685
+ ) {
686
+ tags.add(obj.belongToSchema.serviceTag);
673
687
  }
674
688
  }
675
689
  return Array.from(tags).sort();
@@ -751,7 +765,7 @@ function addStatement(statements, action, resource) {
751
765
 
752
766
  function roleNeedsSchemaRead(state, roleName) {
753
767
  if (!roleName.endsWith('Role') || !roleName) return false;
754
- return roleName !== 'FlowRbacRole' || state.rbac;
768
+ return roleName !== 'FlowRbacRole' || state.selfRbac;
755
769
  }
756
770
 
757
771
  function roleTableNames(state, roleName) {
@@ -767,7 +781,7 @@ function roleTableNames(state, roleName) {
767
781
  'AwaitingMultipleSteps',
768
782
  'AwaitingMultipleStepByPending',
769
783
  'WebSocketTask',
770
- ...(state.rbac
784
+ ...(state.selfRbac
771
785
  ? [
772
786
  'Roles',
773
787
  'TargetRoles',
@@ -958,7 +972,7 @@ function roleTopicNames(state, roleName) {
958
972
  }
959
973
  }
960
974
  }
961
- return topics;
975
+ return [...new Set(topics)];
962
976
  }
963
977
  if (roleName === 'FlowRelationshipRole') {
964
978
  const actions = state.outTopics.filter((topic) =>
@@ -1102,8 +1116,15 @@ function roleExternalLambdaArns(state, roleName) {
1102
1116
  (tag) =>
1103
1117
  `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:${tag}\${self:custom.iz_stage}ValidatorRbacHdrInv`
1104
1118
  );
1119
+ if (roleName === 'FlowObjectsRole') {
1120
+ return [
1121
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}ValidatorRbacHdrInv`,
1122
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}ConfirmUsageHdrInv`,
1123
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}CancelUsageHdrInv`,
1124
+ ...extraLambdas
1125
+ ];
1126
+ }
1105
1127
  if (
1106
- roleName === 'FlowObjectsRole' ||
1107
1128
  roleName === 'FlowRbacRole' ||
1108
1129
  roleName === 'FlowRelationshipRole'
1109
1130
  ) {
@@ -1281,8 +1302,28 @@ function hasSelfRbacAddOn(object) {
1281
1302
  );
1282
1303
  }
1283
1304
 
1284
- function hasRbacAddOn(object) {
1285
- return hasSelfRbacAddOn(object);
1305
+ function hasRbacAddOn(object, objects = []) {
1306
+ const selfHasRbac = hasSelfRbacAddOn(object);
1307
+ const belongToTargets = object?.belongTo
1308
+ ? Array.isArray(object.belongTo)
1309
+ ? object.belongTo
1310
+ : [object.belongTo]
1311
+ : [];
1312
+ const belongToHasRbac =
1313
+ (Array.isArray(object?.belongToSchema?.addOnDataStructure) &&
1314
+ object.belongToSchema.addOnDataStructure.some(
1315
+ (addOn) => addOn?.type === 'rbac'
1316
+ )) ||
1317
+ belongToTargets.some((target) => {
1318
+ if (target?.serviceTag === 'UserAccount') return true;
1319
+ const matched = objects.find(
1320
+ (candidate) =>
1321
+ candidate?.serviceTag === target?.serviceTag &&
1322
+ candidate?.objectType === target?.objectType
1323
+ );
1324
+ return hasSelfRbacAddOn(matched);
1325
+ });
1326
+ return selfHasRbac || belongToHasRbac;
1286
1327
  }
1287
1328
 
1288
1329
  /**
@@ -1292,7 +1333,7 @@ function hasRbacAddOn(object) {
1292
1333
  */
1293
1334
  function rbacFlowQueueNames(state) {
1294
1335
  const flowTags = [
1295
- ...new Set((state.objects ?? []).filter(hasRbacAddOn).map(rbacFlowTag))
1336
+ ...new Set((state.objects ?? []).filter(hasSelfRbacAddOn).map(rbacFlowTag))
1296
1337
  ];
1297
1338
  return flowTags.map((flowTag) => `${flowTag}HdrSqs`);
1298
1339
  }
@@ -106,16 +106,19 @@ test('generates RBAC own-topic WebSocket entries alongside their SQS handlers',
106
106
  ),
107
107
  /ListTargetRole/
108
108
  );
109
- assert.match(
110
- await readFile(
111
- join(
112
- rootPath,
113
- 'app/src/generatedCode/FlowRbac/ValidatorRbac/source/ValidatorRbac_Main.js'
114
- ),
115
- 'utf8'
109
+ const validatorRbac = await readFile(
110
+ join(
111
+ rootPath,
112
+ 'app/src/generatedCode/FlowRbac/ValidatorRbac/source/ValidatorRbac_Main.js'
116
113
  ),
117
- /Fast-path triggered: user self-action/
114
+ 'utf8'
118
115
  );
116
+ assert.match(validatorRbac, /Fast-path triggered: user self-action/);
117
+ assert.match(validatorRbac, /return l1Hit;/);
118
+ assert.match(validatorRbac, /return l2Hit;/);
119
+ assert.match(validatorRbac, /return cacheResult;/);
120
+ assert.doesNotMatch(validatorRbac, /L1 Cache hit:[^;]+;\s*return;/);
121
+ assert.doesNotMatch(validatorRbac, /L2 Cache hit:[^;]+;\s*return;/);
119
122
  assert.match(
120
123
  await readFile(
121
124
  join(