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

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 (26) 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/CreateEndpoint_Main.ejs +33 -27
  8. package/src/emit/crud/templates/CreateLogicalLib.ejs +247 -89
  9. package/src/emit/crud/templates/CreateNodeCallback_Main.ejs +30 -20
  10. package/src/emit/crud/templates/DeleteAfterHookCallback_Main.ejs +118 -0
  11. package/src/emit/crud/templates/DeleteComplete_Main.ejs +124 -10
  12. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +26 -16
  13. package/src/emit/crud/templates/DeleteLogicalLib.ejs +112 -1
  14. package/src/emit/crud/templates/UpdateAfterHookCallback_Main.ejs +120 -0
  15. package/src/emit/crud/templates/UpdateComplete_Main.ejs +104 -10
  16. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +34 -22
  17. package/src/emit/crud/templates/UpdateLogicalLib.ejs +121 -1
  18. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +8 -2
  19. package/src/emit/rbac/templates/CancelUsage_Main.ejs +34 -14
  20. package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +34 -14
  21. package/src/emit/resource/emitResourceArtifacts.js +9 -2
  22. package/test/crudApplicationArtifacts.test.js +345 -0
  23. package/test/dynamicCrudFlowSchemas.test.js +10 -5
  24. package/test/dynamicRbacFlowSchemas.test.js +24 -2
  25. package/test/graphDrivenFlowSchema.test.js +36 -0
  26. package/test/resourceArtifacts.test.js +71 -0
@@ -200,11 +200,15 @@ export default async function <%- flowTag %>(
200
200
 
201
201
  // ── Optional phase extension point ────────────────────────────────────────
202
202
  <%_
203
+ const allCrudObjectTypes = (objects || []).filter(isCrudObject).map(o => o.objectType);
203
204
  const beforeHooksByObjType = (plan?.beforeHooks || []).reduce((acc, hook) => {
204
- const key = hook?.objType?.objectType;
205
- if (!key) return acc;
206
- if (!acc[key]) acc[key] = [];
207
- acc[key].push(hook);
205
+ const targets = (hook?.targetObjectTypes && hook.targetObjectTypes.length > 0)
206
+ ? hook.targetObjectTypes
207
+ : allCrudObjectTypes;
208
+ for (const key of targets) {
209
+ if (!acc[key]) acc[key] = [];
210
+ acc[key].push(hook);
211
+ }
208
212
  return acc;
209
213
  }, {});
210
214
  _%>
@@ -218,6 +222,18 @@ export default async function <%- flowTag %>(
218
222
  const lambdaSyncInvResults = [];
219
223
  <%_ for (const config of configs.filter(c => c?.generatedCodeConfig?.invocationType === 'lambdaSyncInv' || c?.event?.includes('lambdaSyncInv'))) { _%>
220
224
  try {
225
+ let hookPayload_<%= config.flowTag %> = {
226
+ objType,
227
+ objInstanceFull: requestParams.objInstanceFull,
228
+ identifiers: requestParams.objInstanceFull?.identifiers,
229
+ fieldNames:
230
+ requestParams.objInstanceFull?.fields || requestParams.fieldNames,
231
+ relationships: requestParams.relationships || [],
232
+ targetIdentifiers,
233
+ submittedByUserId: requestParams.submittedByUserId
234
+ };
235
+ //(<beforeSendHook_<%= config.flowTag %>>)
236
+ //(</beforeSendHook_<%= config.flowTag %>>)
221
237
  const returnValue = await lambda.invokeSync(
222
238
  _izContext,
223
239
  await lambdaSharedLib.lambdaFunctionName(
@@ -225,15 +241,7 @@ export default async function <%- flowTag %>(
225
241
  '<%= config.flowTag %>'
226
242
  ),
227
243
  {
228
- requestParams: {
229
- objType,
230
- objInstanceFull: requestParams.objInstanceFull,
231
- identifiers: requestParams.objInstanceFull?.identifiers,
232
- fieldNames: requestParams.objInstanceFull?.fields || requestParams.fieldNames,
233
- relationships: requestParams.relationships || [],
234
- targetIdentifiers,
235
- submittedByUserId: requestParams.submittedByUserId
236
- },
244
+ requestParams: hookPayload_<%= config.flowTag %>,
237
245
  callingFlowConfig
238
246
  }
239
247
  );
@@ -282,17 +290,21 @@ export default async function <%- flowTag %>(
282
290
  <%_ if (hasOwnTopic) { _%>
283
291
  // ── ownTopic (async — dispatch to before-hook and await callback) ──────────
284
292
  <%_ configs.filter(c => c?.generatedCodeConfig?.invocationType === 'ownTopic' || c?.event?.includes('ownTopic')).forEach(config => { _%>
293
+ let hookPayload_<%= config.flowTag %> = {
294
+ objType,
295
+ objInstanceFull: requestParams.objInstanceFull,
296
+ identifiers: requestParams.objInstanceFull?.identifiers,
297
+ fieldNames:
298
+ requestParams.objInstanceFull?.fields || requestParams.fieldNames,
299
+ relationships: requestParams.relationships || [],
300
+ targetIdentifiers,
301
+ submittedByUserId: requestParams.submittedByUserId
302
+ };
303
+ //(<beforeSendHook_<%= config.flowTag %>>)
304
+ //(</beforeSendHook_<%= config.flowTag %>>)
285
305
  const hookMsg_<%= config.flowTag %> =
286
306
  callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
287
- {
288
- objType,
289
- objInstanceFull: requestParams.objInstanceFull,
290
- identifiers: requestParams.objInstanceFull?.identifiers,
291
- fieldNames: requestParams.objInstanceFull?.fields || requestParams.fieldNames,
292
- relationships: requestParams.relationships || [],
293
- targetIdentifiers,
294
- submittedByUserId: requestParams.submittedByUserId
295
- },
307
+ hookPayload_<%= config.flowTag %>,
296
308
  callingFlowSharedLib.addParentCallingFlowConfig(
297
309
  callingFlowConfig,
298
310
  callingFlowSharedLib.createCallingFlowConfig(
@@ -852,12 +852,132 @@ export async function <%- flowTag %>LogicalLib(
852
852
 
853
853
  // 5. If no graph storage, complete immediately
854
854
  if (!hasGraphStorage) {
855
+ <%_ if (plan?.hasAfterHookSync) { _%>
856
+ // ── Synchronous After Hooks ──
857
+ <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'lambdaSyncInv').forEach(hook => { _%>
858
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
859
+ if (objType?.objectType === '<%= hook.targetObjectTypes[0] %>') {
860
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
861
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(objType?.objectType)) {
862
+ <%_ } _%>
863
+ try {
864
+ let hookPayload_<%= hook.flowTag %> = {
865
+ objType,
866
+ objInstanceFull: createObjInstanceFullFromParams(
867
+ updateItem,
868
+ requestParams
869
+ ),
870
+ relationships: requestParams.relationships || [],
871
+ targetIdentifiers: requestParams.targetIdentifiers,
872
+ submittedByUserId: requestParams.submittedByUserId
873
+ };
874
+ //(<beforeSendHook_<%= hook.flowTag %>>)
875
+ //(</beforeSendHook_<%= hook.flowTag %>>)
876
+ const hookResult = await lambda.invokeSync(
877
+ _izContext,
878
+ await lambdaSharedLib.lambdaFunctionName(
879
+ _izContext,
880
+ '<%= hook.flowTag %>'
881
+ ),
882
+ {
883
+ requestParams: hookPayload_<%= hook.flowTag %>,
884
+ callingFlowConfig
885
+ }
886
+ );
887
+ if (
888
+ hookResult?.errorsFound?.length > 0 ||
889
+ hookResult?.status === 'error'
890
+ ) {
891
+ errorsFound.push(
892
+ ...(hookResult.errorsFound || ['Hook <%= hook.flowTag %> failed'])
893
+ );
894
+ }
895
+ } catch (hookErr) {
896
+ _izContext.logger.error(
897
+ '[<%- flowTag %>LogicalLib] Sync hook <%= hook.flowTag %> error:',
898
+ hookErr
899
+ );
900
+ errorsFound.push(hookErr.message || 'Hook <%= hook.flowTag %> failed');
901
+ }
902
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
903
+ }
904
+ <%_ } _%>
905
+ <%_ }) _%>
906
+ <%_ } _%>
907
+
908
+ <%_ if (plan?.hasAfterHookAsync) { _%>
909
+ // ── Asynchronous After Hooks ──
910
+ <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'ownTopic').forEach(hook => { _%>
911
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
912
+ if (objType?.objectType === '<%= hook.targetObjectTypes[0] %>') {
913
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
914
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(objType?.objectType)) {
915
+ <%_ } _%>
916
+ let hookPayload_<%= hook.flowTag %> = {
917
+ objType,
918
+ objInstanceFull: createObjInstanceFullFromParams(
919
+ updateItem,
920
+ requestParams
921
+ ),
922
+ relationships: requestParams.relationships || [],
923
+ targetIdentifiers: requestParams.targetIdentifiers,
924
+ submittedByUserId: requestParams.submittedByUserId
925
+ };
926
+ //(<beforeSendHook_<%= hook.flowTag %>>)
927
+ //(</beforeSendHook_<%= hook.flowTag %>>)
928
+ const hookMsg_<%= hook.flowTag %> =
929
+ callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
930
+ hookPayload_<%= hook.flowTag %>,
931
+ callingFlowSharedLib.addParentCallingFlowConfig(
932
+ callingFlowConfig,
933
+ callingFlowSharedLib.createCallingFlowConfig(
934
+ await lambdaSharedLib.lambdaFunctionName(
935
+ _izContext,
936
+ 'UpdateAfterHookCallbackHdrSqs'
937
+ ),
938
+ {
939
+ actionData: {
940
+ objType,
941
+ objInstanceFull: createObjInstanceFullFromParams(
942
+ updateItem,
943
+ requestParams
944
+ ),
945
+ relationships: requestParams.relationships || [],
946
+ targetIdentifiers: requestParams.targetIdentifiers,
947
+ parentFlowId: requestParams.parentFlowId,
948
+ status: 'complete',
949
+ errorsFound
950
+ }
951
+ }
952
+ )
953
+ )
954
+ );
955
+
956
+ await sns.publishAsync(_izContext, {
957
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
958
+ _izContext,
959
+ '<%= hook.flowTag %>_In'
960
+ ),
961
+ Message: JSON.stringify(hookMsg_<%= hook.flowTag %>),
962
+ MessageAttributes: sns.createStringMessageAttributes(_izContext, {})
963
+ });
964
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
965
+ }
966
+ <%_ } _%>
967
+ <%_ }) _%>
968
+ <%_ } else { _%>
855
969
  await handleDynamoDbOnlyComplete(_izContext, {
856
970
  objType,
857
971
  updateItem,
858
972
  requestParams,
859
973
  callingFlowConfig
860
974
  });
975
+ <%_ } _%>
976
+ }
977
+
978
+ let resultStatus = 'complete';
979
+ if (errorsFound.length > 0) {
980
+ resultStatus = 'error';
861
981
  }
862
982
 
863
983
  return {
@@ -866,7 +986,7 @@ export async function <%- flowTag %>LogicalLib(
866
986
  objInstanceFull: { identifiers, fields },
867
987
  versionedDataIds
868
988
  },
869
- status: 'complete',
989
+ status: resultStatus,
870
990
  errorsFound
871
991
  };
872
992
  } catch (err) {
@@ -19,8 +19,14 @@ const RBAC_CORE_FLOW_DEFINITIONS = [
19
19
 
20
20
  const LIMIT_FLOW_DEFINITIONS = [
21
21
  { flowTag: 'ReservedDynamicUsage', event: ['ownTopic'] },
22
- { flowTag: 'ConfirmUsage', event: ['ownTopic'] },
23
- { flowTag: 'CancelUsage', event: ['ownTopic'] }
22
+ {
23
+ flowTag: 'ConfirmUsage',
24
+ event: ['ownTopic', 'lambdaSyncInv', 'lambdaSyncApi']
25
+ },
26
+ {
27
+ flowTag: 'CancelUsage',
28
+ event: ['ownTopic', 'lambdaSyncInv', 'lambdaSyncApi']
29
+ }
24
30
  ];
25
31
 
26
32
  const RBAC_FLOW_DEFINITIONS = [
@@ -146,20 +146,40 @@ export default async function CancelUsage(_izContext, requestParams) {
146
146
  }
147
147
  }
148
148
 
149
- await sns.publishMessage(_izContext, {
150
- message: {
151
- returnResult: {
152
- status: true,
153
- uniqueIdentify: usedTimestampId,
154
- usedTimestampId,
155
- runningTotalReservedId
156
- }
157
- },
158
- TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
159
- _izContext,
160
- 'CancelUsage_Out'
161
- )
162
- });
149
+ const returnResult = {
150
+ status: true,
151
+ uniqueIdentify: usedTimestampId,
152
+ usedTimestampId,
153
+ runningTotalReservedId
154
+ };
155
+
156
+ const eventSourceTag =
157
+ _izContext.izEventSource?.eventSourceTag ||
158
+ requestParams?.izEventSource?.eventSourceTag;
159
+
160
+ let isSync = false;
161
+ if (
162
+ eventSourceTag === 'DirectInvoke' ||
163
+ eventSourceTag === 'ApiGateway' ||
164
+ eventSourceTag === 'ApiGatewayV2' ||
165
+ requestParams?.hasOwnProperty('__context__')
166
+ ) {
167
+ isSync = true;
168
+ }
169
+
170
+ if (!isSync) {
171
+ await sns.publishMessage(_izContext, {
172
+ message: {
173
+ returnResult
174
+ },
175
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
176
+ _izContext,
177
+ 'CancelUsage_Out'
178
+ )
179
+ });
180
+ }
181
+
182
+ return returnResult;
163
183
  } catch (error) {
164
184
  _izContext.logger.error(`[${process.env.iz_serviceTag}:CancelUsage] error:`, error);
165
185
  throw error;
@@ -170,20 +170,40 @@ export default async function ConfirmUsage(_izContext, requestParams) {
170
170
  ).catch(() => null);
171
171
  }
172
172
 
173
- await sns.publishMessage(_izContext, {
174
- message: {
175
- returnResult: {
176
- status: true,
177
- uniqueIdentify: usedTimestampId,
178
- usedTimestampId,
179
- runningTotalReservedId
180
- }
181
- },
182
- TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
183
- _izContext,
184
- 'ConfirmUsage_Out'
185
- )
186
- });
173
+ const returnResult = {
174
+ status: true,
175
+ uniqueIdentify: usedTimestampId,
176
+ usedTimestampId,
177
+ runningTotalReservedId
178
+ };
179
+
180
+ const eventSourceTag =
181
+ _izContext.izEventSource?.eventSourceTag ||
182
+ requestParams?.izEventSource?.eventSourceTag;
183
+
184
+ let isSync = false;
185
+ if (
186
+ eventSourceTag === 'DirectInvoke' ||
187
+ eventSourceTag === 'ApiGateway' ||
188
+ eventSourceTag === 'ApiGatewayV2' ||
189
+ requestParams?.hasOwnProperty('__context__')
190
+ ) {
191
+ isSync = true;
192
+ }
193
+
194
+ if (!isSync) {
195
+ await sns.publishMessage(_izContext, {
196
+ message: {
197
+ returnResult
198
+ },
199
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
200
+ _izContext,
201
+ 'ConfirmUsage_Out'
202
+ )
203
+ });
204
+ }
205
+
206
+ return returnResult;
187
207
  } catch (error) {
188
208
  _izContext.logger.error(`[${process.env.iz_serviceTag}:ConfirmUsage] error:`, error);
189
209
  throw error;
@@ -958,7 +958,7 @@ function roleTopicNames(state, roleName) {
958
958
  }
959
959
  }
960
960
  }
961
- return topics;
961
+ return [...new Set(topics)];
962
962
  }
963
963
  if (roleName === 'FlowRelationshipRole') {
964
964
  const actions = state.outTopics.filter((topic) =>
@@ -1102,8 +1102,15 @@ function roleExternalLambdaArns(state, roleName) {
1102
1102
  (tag) =>
1103
1103
  `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:${tag}\${self:custom.iz_stage}ValidatorRbacHdrInv`
1104
1104
  );
1105
+ if (roleName === 'FlowObjectsRole') {
1106
+ return [
1107
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}ValidatorRbacHdrInv`,
1108
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}ConfirmUsageHdrInv`,
1109
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}CancelUsageHdrInv`,
1110
+ ...extraLambdas
1111
+ ];
1112
+ }
1105
1113
  if (
1106
- roleName === 'FlowObjectsRole' ||
1107
1114
  roleName === 'FlowRbacRole' ||
1108
1115
  roleName === 'FlowRelationshipRole'
1109
1116
  ) {