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

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 (61) hide show
  1. package/package.json +2 -2
  2. package/src/compile/flowValidator.js +163 -0
  3. package/src/compile/loadSchemas.js +15 -5
  4. package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +137 -7
  5. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +132 -17
  6. package/src/emit/crud/crudActionPlan.js +82 -0
  7. package/src/emit/crud/emitCrudApplicationArtifacts.js +162 -112
  8. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +43 -65
  9. package/src/emit/crud/generateDynamicCrudFlowSchemas.js +4 -1
  10. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +84 -512
  11. package/src/emit/crud/templates/CreateLogicalLib.ejs +536 -0
  12. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +87 -202
  13. package/src/emit/crud/templates/DeleteLogicalLib.ejs +213 -0
  14. package/src/emit/crud/templates/FlowEndpointRbacCallback_Main.ejs +217 -0
  15. package/src/emit/crud/templates/GetEndpoint_Main.ejs +1 -1
  16. package/src/emit/crud/templates/GetRangeEndpoint_Main.ejs +9 -2
  17. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +87 -444
  18. package/src/emit/crud/templates/UpdateLogicalLib.ejs +472 -0
  19. package/src/emit/custom/customFlows.js +27 -2
  20. package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +1 -2
  21. package/src/emit/custom/templates/FlowEndpoint_Main.ejs +10 -3
  22. package/src/emit/custom/templates/FlowStep_Main.ejs +1 -1
  23. package/src/emit/custom/templates/s3/CreateObject_Main.ejs +1 -1
  24. package/src/emit/custom/templates/triggerCache/CheckTriggerCache_HdrSqs.ejs +2 -2
  25. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +3 -9
  26. package/src/emit/rbac/emitRbacApplicationArtifacts.js +21 -16
  27. package/src/emit/rbac/rbacFlowTag.js +2 -7
  28. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +148 -21
  29. package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +4 -2
  30. package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +1 -1
  31. package/src/emit/relationship/templates/CreateRelationship_Main.ejs +1 -1
  32. package/src/emit/relationship/templates/DeleteRelationship_Main.ejs +1 -1
  33. package/src/emit/relationship/templates/GetRelationship_Main.ejs +1 -1
  34. package/src/emit/relationship/templates/MoveRelationship_Main.ejs +1 -1
  35. package/src/emit/relationship/templates/UpdateRelationship_Main.ejs +1 -1
  36. package/src/emit/resource/emitResourceArtifacts.js +83 -68
  37. package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +31 -5
  38. package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +21 -0
  39. package/src/emit/resource/templates/WebSocketSubscription_Yaml.ejs +7 -0
  40. package/src/emit/shared/templates/FlowHandler_HdrApi.ejs +1 -1
  41. package/src/emit/shared/templates/FlowHandler_HdrDsq.ejs +2 -2
  42. package/src/emit/shared/templates/FlowHandler_HdrInv.ejs +1 -1
  43. package/src/emit/shared/templates/FlowHandler_HdrSqs.ejs +2 -2
  44. package/src/emit/systemText/emitSystemTextSchemas.js +246 -0
  45. package/src/emit/systemText/templates/SystemTextSchemas.ejs +10 -0
  46. package/src/generate.js +55 -8
  47. package/test/crudApplicationArtifacts.test.js +147 -19
  48. package/test/customFlowApplicationArtifacts.test.js +308 -16
  49. package/test/dynamicCrudFlowSchemas.test.js +150 -14
  50. package/test/dynamicRbacFlowSchemas.test.js +2 -2
  51. package/test/dynamicRelationshipFlowSchemas.test.js +1 -1
  52. package/test/graphDrivenFlowSchema.test.js +257 -0
  53. package/test/publicApi.test.js +14 -5
  54. package/test/resourceArtifacts.test.js +25 -19
  55. package/test/systemTextSchemas.test.js +154 -0
  56. package/src/emit/crud/templates/CreateComplete_HdrSqs.ejs +0 -99
  57. package/src/emit/crud/templates/DeleteComplete_HdrSqs.ejs +0 -99
  58. package/src/emit/crud/templates/FlowEndpointBeforeLogical_Main.ejs +0 -174
  59. package/src/emit/crud/templates/FlowEndpointComplete_Main.ejs +0 -187
  60. package/src/emit/crud/templates/UpdateComplete_HdrSqs.ejs +0 -99
  61. package/src/emit/resource/templates/ResourceBeforeLogicalYaml.ejs +0 -47
@@ -17,11 +17,12 @@ export async function emitRbacApplicationArtifacts(objects) {
17
17
  if (!Array.isArray(objects)) {
18
18
  throw new TypeError('objects must be an array');
19
19
  }
20
- if (!objects.some(hasRbacAddOn)) {
20
+ const rbacObjects = objects.filter(hasRbacAddOn);
21
+ if (rbacObjects.length === 0) {
21
22
  return [];
22
23
  }
23
-
24
24
  const hasSelfRbac = objects.some(hasSelfRbacAddOn);
25
+ const flowTags = [...new Set(rbacObjects.map(rbacFlowTag))];
25
26
  const handlers = await Promise.all([
26
27
  ...(hasSelfRbac
27
28
  ? [
@@ -31,7 +32,7 @@ export async function emitRbacApplicationArtifacts(objects) {
31
32
  ...USER_ROLE_MUTATIONS.map(createUserRoleMutationHandler)
32
33
  ]
33
34
  : []),
34
- ...objects.filter(hasRbacAddOn).map(createUserRbacFlowHandler)
35
+ ...flowTags.map(createUserRbacFlowHandler)
35
36
  ]);
36
37
  return [
37
38
  ...handlers.flatMap((handler) => handler.artifacts),
@@ -93,6 +94,10 @@ const STATIC_RBAC_APPLICATION_PATHS = [
93
94
  'app/src/generatedCode/FlowRbac/CreateTargetRole/source/CreateTargetRole_HdrSqs.js',
94
95
  'app/src/generatedCode/FlowRbac/CreateTargetRole/source/CreateTargetRole_WbsMain.js',
95
96
  'app/src/generatedCode/FlowRbac/CreateTargetRole/source/CreateTargetRole_HdrWbs.js',
97
+ 'app/src/generatedCode/FlowRbac/ValidatorRbac/source/ValidatorRbac_Main.js',
98
+ 'app/src/generatedCode/FlowRbac/ValidatorRbac/source/ValidatorRbac_HdrSqs.js',
99
+ 'app/src/generatedCode/FlowRbac/ValidatorRbac/source/ValidatorRbac_WbsMain.js',
100
+ 'app/src/generatedCode/FlowRbac/ValidatorRbac/source/ValidatorRbac_HdrWbs.js',
96
101
  ...LIST_DEFINITIONS.flatMap(({ flowTag }) => [
97
102
  `app/src/generatedCode/FlowRbac/${flowTag}/source/${flowTag}_Main.js`,
98
103
  `app/src/generatedCode/FlowRbac/${flowTag}/source/${flowTag}_HdrSqs.js`,
@@ -118,12 +123,15 @@ export function rbacApplicationManagedPaths(objects = []) {
118
123
  throw new TypeError('objects must be an array');
119
124
  }
120
125
  const rbacObjects = objects.filter(hasRbacAddOn);
121
- const sourcePaths = rbacObjects.flatMap((object) => {
122
- const flowTag = rbacFlowTag(object);
126
+ const flowTags = [...new Set(rbacObjects.map(rbacFlowTag))];
127
+ const sourcePaths = flowTags.flatMap((flowTag) => {
123
128
  const sourcePath = `app/src/generatedCode/FlowRbac/${flowTag}/source`;
124
- return [`${sourcePath}/${flowTag}_Main.js`, `${sourcePath}/${flowTag}_HdrSqs.js`];
129
+ return [
130
+ `${sourcePath}/${flowTag}_Main.js`,
131
+ `${sourcePath}/${flowTag}_HdrSqs.js`
132
+ ];
125
133
  });
126
- return [...STATIC_RBAC_APPLICATION_PATHS, ...sourcePaths];
134
+ return [...new Set([...STATIC_RBAC_APPLICATION_PATHS, ...sourcePaths])];
127
135
  }
128
136
 
129
137
  async function createTargetRoleHandler() {
@@ -276,8 +284,11 @@ async function createUserRoleMutationHandler(definition) {
276
284
  };
277
285
  }
278
286
 
279
- async function createUserRbacFlowHandler(object) {
280
- const flowTag = rbacFlowTag(object);
287
+ async function createUserRbacFlowHandler(flowTagOrObject) {
288
+ const flowTag =
289
+ typeof flowTagOrObject === 'string'
290
+ ? flowTagOrObject
291
+ : rbacFlowTag(flowTagOrObject);
281
292
  const sourcePath = `app/src/generatedCode/FlowRbac/${flowTag}/source`;
282
293
  const handlerPath = `src/generatedCode/FlowRbac/${flowTag}/source`;
283
294
  return {
@@ -374,11 +385,5 @@ function hasSelfRbacAddOn(object) {
374
385
  }
375
386
 
376
387
  function hasRbacAddOn(object) {
377
- const selfHasRbac = hasSelfRbacAddOn(object);
378
- const belongToHasRbac =
379
- Array.isArray(object?.belongToSchema?.addOnDataStructure) &&
380
- object.belongToSchema.addOnDataStructure.some(
381
- (addOn) => addOn?.type === 'rbac'
382
- );
383
- return selfHasRbac || belongToHasRbac;
388
+ return hasSelfRbacAddOn(object);
384
389
  }
@@ -1,9 +1,4 @@
1
1
  export function rbacFlowTag(object) {
2
- const objectType = object?.objectType;
3
- if (typeof objectType !== 'string' || objectType.trim() === '') {
4
- throw new TypeError(
5
- 'RBAC ObjectSchema.objectType must be a non-empty string'
6
- );
7
- }
8
- return `${objectType[0].toUpperCase()}${objectType.slice(1)}RbacFlow`;
2
+ return 'ValidatorRbac';
9
3
  }
4
+
@@ -16,22 +16,121 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
18
  import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
19
- import { coreConsts } from '@izara_project/izara-core-library-core';
19
+ import { consts } from '@izara_project/izara-core-library-core';
20
20
  import { sns } from '@izara_project/izara-core-library-external-request';
21
21
  import snsSharedLib from '@izara_project/izara-core-library-sns';
22
22
 
23
+ // ── L1 Cache (In-Memory per Lambda Container Instance) ────────────────────────
24
+ const L1_TTL_MS = 5 * 60 * 1000; // 5 minutes
25
+ const l1Cache = new Map();
26
+
27
+ function _getL1Cache(key) {
28
+ const entry = l1Cache.get(key);
29
+ if (!entry) return null;
30
+ if (entry.expiresAt < Date.now()) {
31
+ l1Cache.delete(key);
32
+ return null;
33
+ }
34
+ return entry;
35
+ }
36
+
37
+ function _setL1Cache(key, value) {
38
+ if (l1Cache.size > 500) {
39
+ const oldestKey = l1Cache.keys().next().value;
40
+ if (oldestKey) l1Cache.delete(oldestKey);
41
+ }
42
+ l1Cache.set(key, {
43
+ ...value,
44
+ expiresAt: Date.now() + L1_TTL_MS
45
+ });
46
+ }
47
+
48
+ // ── L2 Cache (Shared DynamoDB TTL Cache) ──────────────────────────────────────
49
+ const L2_TTL_SECONDS = 5 * 60; // 5 minutes
50
+
51
+ async function _getL2Cache(_izContext, userId, cacheSubKey) {
52
+ try {
53
+ const tableName = dynamodbSharedLib.tableName(_izContext, 'RbacPermissionCaches');
54
+ const result = await dynamodbSharedLib.getItem(_izContext, tableName, {
55
+ userId,
56
+ cacheKey: cacheSubKey
57
+ });
58
+ if (result && result.ttlTimestamp && result.ttlTimestamp > Math.floor(Date.now() / 1000)) {
59
+ return {
60
+ returnValue: {
61
+ permission: Boolean(result.permission),
62
+ roleIdKey: result.roleIdKey || '',
63
+ rbacType: result.rbacType || 'target'
64
+ },
65
+ errorsFound: [],
66
+ status: 'Completed'
67
+ };
68
+ }
69
+ } catch (error) {
70
+ // If L2 cache table does not exist or fails, safely ignore and continue to DB
71
+ }
72
+ return null;
73
+ }
74
+
75
+ async function _setL2Cache(_izContext, userId, cacheSubKey, value) {
76
+ try {
77
+ const tableName = dynamodbSharedLib.tableName(_izContext, 'RbacPermissionCaches');
78
+ const ttlTimestamp = Math.floor(Date.now() / 1000) + L2_TTL_SECONDS;
79
+ await dynamodbSharedLib.putItem(_izContext, tableName, {
80
+ userId,
81
+ cacheKey: cacheSubKey,
82
+ permission: value.returnValue ? value.returnValue.permission : value.permission,
83
+ roleIdKey: value.returnValue ? value.returnValue.roleIdKey : value.roleIdKey,
84
+ rbacType: 'target',
85
+ ttlTimestamp
86
+ });
87
+ } catch (error) {
88
+ // Safely ignore L2 write errors if table is not configured
89
+ }
90
+ }
91
+
23
92
  export default async function <%- flowTag %>(_izContext, requestParams = {}) {
24
- const userId = _izContext.correlationIds.get(coreConsts.BASE_USER_ID);
25
- const targetId = _izContext.correlationIds.get(coreConsts.TARGET_ID);
93
+ const userId = _izContext.correlationIds.get(consts.BASE_USER_ID);
94
+ const targetId = _izContext.correlationIds.get(consts.TARGET_ID);
26
95
  if (!userId || !targetId) {
27
96
  throw new Error('Missing required request parameters: userId or targetId');
28
97
  }
29
98
 
30
- const { flowTypeConcat, inputParameterGroups } = requestParams;
99
+ const { flowTypeConcat, inputParameterGroups, parentFlowId } = requestParams;
31
100
  if (!flowTypeConcat) {
32
101
  throw new Error('Missing flowTypeConcat in requestParams');
33
102
  }
34
103
 
104
+ _izContext.logger.debug('[<%- flowTag %>] requestParams:', {
105
+ userId,
106
+ targetId,
107
+ flowTypeConcat,
108
+ inputParameterGroups
109
+ });
110
+
111
+ const cacheSubKey = `${targetId}#${flowTypeConcat}#${JSON.stringify(inputParameterGroups || {})}`;
112
+ const fullCacheKey = `${userId}:${cacheSubKey}`;
113
+
114
+ // 1. Check L1 In-Memory Cache
115
+ const l1Hit = _getL1Cache(fullCacheKey);
116
+ if (l1Hit) {
117
+ _izContext.logger.debug('[<%- flowTag %>] L1 Cache hit:', l1Hit);
118
+ await _publicMessageOut(_izContext, { ...l1Hit, parentFlowId });
119
+ return;
120
+ }
121
+
122
+ // 2. Check L2 Shared DynamoDB Cache
123
+ const l2Hit = await _getL2Cache(_izContext, userId, cacheSubKey);
124
+ if (l2Hit) {
125
+ _izContext.logger.debug('[<%- flowTag %>] L2 Cache hit:', l2Hit);
126
+ _setL1Cache(fullCacheKey, l2Hit);
127
+ await _publicMessageOut(_izContext, { ...l2Hit, parentFlowId });
128
+ return;
129
+ }
130
+
131
+ _izContext.logger.debug('[<%- flowTag %>] Cache miss — querying DynamoDB');
132
+
133
+ // 3. Fallback to DB Query
35
134
  const userRolesResult = await dynamodbSharedLib.query(
36
135
  _izContext,
37
136
  dynamodbSharedLib.tableName(_izContext, 'UserRoles'),
@@ -45,6 +144,8 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
45
144
  userRoles = userRolesResult.Items;
46
145
  }
47
146
 
147
+ _izContext.logger.debug('[<%- flowTag %>] userRoles fetched:', { userRoles });
148
+
48
149
  const targetIdPrefix = `${targetId}_`;
49
150
  const targetRoles = [];
50
151
  for (const role of userRoles) {
@@ -53,11 +154,25 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
53
154
  }
54
155
  }
55
156
 
157
+ _izContext.logger.debug('[<%- flowTag %>] targetRoles filtered:', {
158
+ targetIdPrefix,
159
+ targetRoles
160
+ });
161
+
56
162
  if (targetRoles.length === 0) {
57
- await _publicMessageOut(_izContext, {
58
- permission: false,
59
- roleIdKey: ''
60
- });
163
+ _izContext.logger.debug('[<%- flowTag %>] no targetRoles found, permission denied');
164
+ const result = {
165
+ returnValue: {
166
+ permission: false,
167
+ roleIdKey: '',
168
+ rbacType: 'target'
169
+ },
170
+ errorsFound: [],
171
+ status: 'Completed'
172
+ };
173
+ _setL1Cache(fullCacheKey, result);
174
+ await _setL2Cache(_izContext, userId, cacheSubKey, result);
175
+ await _publicMessageOut(_izContext, { ...result, parentFlowId });
61
176
  return;
62
177
  }
63
178
 
@@ -74,6 +189,10 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
74
189
  })
75
190
  );
76
191
 
192
+ _izContext.logger.debug('[<%- flowTag %>] rolePermissions fetched:', {
193
+ rolePermissions
194
+ });
195
+
77
196
  let matched = null;
78
197
  for (const permission of rolePermissions) {
79
198
  if (permission && permission.permission === true) {
@@ -88,17 +207,25 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
88
207
  }
89
208
  }
90
209
 
91
- if (matched) {
92
- await _publicMessageOut(_izContext, {
93
- permission: true,
94
- roleIdKey: matched.roleIdKey
95
- });
96
- } else {
97
- await _publicMessageOut(_izContext, {
98
- permission: false,
99
- roleIdKey: ''
100
- });
101
- }
210
+ const cacheResult = {
211
+ returnValue: {
212
+ permission: Boolean(matched),
213
+ roleIdKey: matched ? matched.roleIdKey : '',
214
+ rbacType: 'target'
215
+ },
216
+ errorsFound: [],
217
+ status: 'Completed'
218
+ };
219
+
220
+ _izContext.logger.debug('[<%- flowTag %>] validation result:', cacheResult);
221
+
222
+ // Store in L1 & L2 Cache
223
+ _setL1Cache(fullCacheKey, cacheResult);
224
+ await _setL2Cache(_izContext, userId, cacheSubKey, cacheResult);
225
+
226
+ await _publicMessageOut(_izContext, { ...cacheResult, parentFlowId });
227
+
228
+ return 'Completed';
102
229
  }
103
230
 
104
231
  async function _publicMessageOut(_izContext, message) {
@@ -108,8 +235,8 @@ async function _publicMessageOut(_izContext, message) {
108
235
  );
109
236
 
110
237
  await sns.publishAsync(_izContext, {
111
- topicArn: topicOut,
112
- message: JSON.stringify(message)
238
+ TopicArn: topicOut,
239
+ Message: JSON.stringify(message)
113
240
  });
114
241
  }
115
242
 
@@ -49,11 +49,12 @@ function createDynamicRelationshipFlow(action) {
49
49
  const outputTopic = action !== 'GetRelationship';
50
50
  const flowSteps = [
51
51
  {
52
- stepName: 'start',
52
+ stepName: 'Start',
53
53
  ...(outputTopic
54
54
  ? {
55
55
  nextSteps: [
56
56
  {
57
+ targetType: 'ownTopic',
57
58
  stepName: 'Complete'
58
59
  }
59
60
  ]
@@ -78,7 +79,8 @@ function createDynamicRelationshipFlow(action) {
78
79
  event: ['extTopic'],
79
80
  previousSteps: [
80
81
  {
81
- stepName: 'start'
82
+ sourceType: 'ownTopic',
83
+ stepName: 'Start'
82
84
  }
83
85
  ],
84
86
  properties: [
@@ -64,7 +64,7 @@ import { consts as coreConsts } from '@izara_project/izara-core-library-core';
64
64
  export default async function <%- functionName %>(
65
65
  _izContext,
66
66
  requestParams,
67
- callingFlowConfig = {}
67
+ callingFlowConfig = {},
68
68
  //(<additionalParams>)
69
69
  //(</additionalParams>)
70
70
  ) {
@@ -61,7 +61,7 @@ const { PREFIX, TOPIC_NAME_GENERATE_CODE, TOPIC_NAME_GRAPH_HANDLER } = consts;
61
61
  export default async function createRelationship(
62
62
  _izContext,
63
63
  requestParams,
64
- callingFlowConfig = {}
64
+ callingFlowConfig = {},
65
65
  //(<additionalParams>)
66
66
  //(</additionalParams>)
67
67
  ) {
@@ -61,7 +61,7 @@ const { PREFIX, TOPIC_NAME_GENERATE_CODE, TOPIC_NAME_GRAPH_HANDLER } = consts;
61
61
  export default async function deleteRelationship(
62
62
  _izContext,
63
63
  requestParams,
64
- callingFlowConfig = {}
64
+ callingFlowConfig = {},
65
65
  //(<additionalParams>)
66
66
  //(</additionalParams>)
67
67
  ) {
@@ -59,7 +59,7 @@ const { PREFIX, TOPIC_NAME_GENERATE_CODE, TOPIC_NAME_GRAPH_HANDLER } = consts;
59
59
  export default async function getRelationship(
60
60
  _izContext,
61
61
  requestParams,
62
- callingFlowConfig = {}
62
+ callingFlowConfig = {},
63
63
  //(<additionalParams>)
64
64
  //(</additionalParams>)
65
65
  ) {
@@ -69,7 +69,7 @@ import { consts as coreConsts } from '@izara_project/izara-core-library-core';
69
69
  export default async function moveRelationship(
70
70
  _izContext,
71
71
  requestParams,
72
- callingFlowConfig = {}
72
+ callingFlowConfig = {},
73
73
  //(<additionalParams>)
74
74
  //(</additionalParams>)
75
75
  ) {
@@ -62,7 +62,7 @@ const { PREFIX, TOPIC_NAME_GENERATE_CODE, TOPIC_NAME_GRAPH_HANDLER } = consts;
62
62
  export default async function updateRelationship(
63
63
  _izContext,
64
64
  requestParams,
65
- callingFlowConfig = {}
65
+ callingFlowConfig = {},
66
66
  //(<additionalParams>)
67
67
  //(</additionalParams>)
68
68
  ) {