@izara_project/izara-core-generate-service-code 1.0.87 → 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 (32) hide show
  1. package/package.json +1 -1
  2. package/src/compile/flowValidator.js +25 -0
  3. package/src/compile/loadSchemas.js +199 -10
  4. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +3 -7
  5. package/src/emit/crud/crudActionPlan.js +105 -6
  6. package/src/emit/crud/emitCrudApplicationArtifacts.js +63 -3
  7. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +15 -32
  8. package/src/emit/crud/templates/CreateAfterHookCallback_Main.ejs +121 -0
  9. package/src/emit/crud/templates/CreateBeforeHookCallback_Main.ejs +108 -0
  10. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +140 -340
  11. package/src/emit/crud/templates/CreateLogicalLib.ejs +338 -82
  12. package/src/emit/crud/templates/CreateNodeCallback_Main.ejs +90 -0
  13. package/src/emit/crud/templates/DeleteAfterHookCallback_Main.ejs +118 -0
  14. package/src/emit/crud/templates/DeleteBeforeHookCallback_Main.ejs +99 -0
  15. package/src/emit/crud/templates/DeleteComplete_Main.ejs +124 -10
  16. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +151 -179
  17. package/src/emit/crud/templates/DeleteLogicalLib.ejs +244 -27
  18. package/src/emit/crud/templates/UpdateAfterHookCallback_Main.ejs +120 -0
  19. package/src/emit/crud/templates/UpdateBeforeHookCallback_Main.ejs +112 -0
  20. package/src/emit/crud/templates/UpdateComplete_Main.ejs +104 -10
  21. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +166 -185
  22. package/src/emit/crud/templates/UpdateLogicalLib.ejs +123 -3
  23. package/src/emit/custom/customFlows.js +3 -1
  24. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +8 -2
  25. package/src/emit/rbac/templates/CancelUsage_Main.ejs +34 -14
  26. package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +34 -14
  27. package/src/emit/resource/emitResourceArtifacts.js +119 -59
  28. package/test/crudApplicationArtifacts.test.js +876 -0
  29. package/test/dynamicCrudFlowSchemas.test.js +10 -5
  30. package/test/dynamicRbacFlowSchemas.test.js +24 -2
  31. package/test/graphDrivenFlowSchema.test.js +36 -0
  32. package/test/resourceArtifacts.test.js +271 -12
@@ -102,29 +102,33 @@ export async function resolveSchemaAndIdentity(_izContext, requestParams, errors
102
102
  targetIdentifiers.submittedByUserId = submittedByUserId;
103
103
  }
104
104
 
105
- if (Array.isArray(objectSchema.identifiers)) {
106
- for (const identifier of objectSchema.identifiers) {
107
- if (identifier.fieldName) {
108
- if (targetIdentifiers[identifier.fieldName] === undefined) {
109
- targetIdentifiers[identifier.fieldName] = null;
110
- }
111
- } else if (Array.isArray(identifier.fieldNames)) {
112
- for (const fn of identifier.fieldNames) {
113
- if (targetIdentifiers[fn] === undefined) {
114
- targetIdentifiers[fn] = null;
115
- }
116
- }
117
- } else if (identifier.name) {
118
- if (targetIdentifiers[identifier.name] === undefined) {
119
- targetIdentifiers[identifier.name] = null;
120
- }
121
- }
122
- }
123
- }
105
+ // if (Array.isArray(objectSchema.identifiers)) {
106
+ // for (const identifier of objectSchema.identifiers) {
107
+ // if (identifier.fieldName) {
108
+ // if (targetIdentifiers[identifier.fieldName] === undefined) {
109
+ // targetIdentifiers[identifier.fieldName] = null;
110
+ // }
111
+ // } else if (Array.isArray(identifier.fieldNames)) {
112
+ // for (const fn of identifier.fieldNames) {
113
+ // if (targetIdentifiers[fn] === undefined) {
114
+ // targetIdentifiers[fn] = null;
115
+ // }
116
+ // }
117
+ // } else if (identifier.name) {
118
+ // if (targetIdentifiers[identifier.name] === undefined) {
119
+ // targetIdentifiers[identifier.name] = null;
120
+ // }
121
+ // }
122
+ // }
123
+ // }
124
124
 
125
125
  requestParams.targetIdentifiers = targetIdentifiers;
126
126
  }
127
127
 
128
+ if (requestParams.rbacTargetIdentifiers === undefined && targetIdentifiers) {
129
+ requestParams.rbacTargetIdentifiers = targetIdentifiers;
130
+ }
131
+
128
132
  _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] Identity & Target Context Resolved:`, {
129
133
  submittedByUserId: requestParams.submittedByUserId,
130
134
  targetIdentifiers: requestParams.targetIdentifiers,
@@ -136,54 +140,165 @@ export async function resolveSchemaAndIdentity(_izContext, requestParams, errors
136
140
  }
137
141
 
138
142
  requestParams.submittedByUserId = submittedByUserId;
139
- return { objType, objectSchema };
143
+ return { objType, objectSchema, targetIdentifiers };
140
144
  }
141
145
 
142
146
  /**
143
- * Resolves and generates primary identifiers for the object
147
+ * Resolves fromRequiredOnCreate fields and generates identifiers
144
148
  * @param {Object} _izContext - Izara context object
145
- * @param {Object} objectSchemas - Combined object schema
146
- * @param {Object} requestParams - Incoming request parameters
147
- * @returns {Promise<void>}
149
+ * @param {Object} requestParams - Request parameters
150
+ * @param {Object} objectSchema - Base object schema
151
+ * @param {Array<string>} errorsFound - Error list
152
+ * @returns {Promise<{ objectSchemas: Object }>}
148
153
  */
149
- export async function resolveAndGenerateIdentifiers(_izContext, objectSchemas, requestParams) {
150
- const identifiers = await createIdentifiersByUseCase(
151
- _izContext,
152
- objectSchemas,
153
- requestParams.fieldNames
154
+ export async function resolveAndGenerateIdentifiers(
155
+ _izContext,
156
+ requestParams,
157
+ objectSchema,
158
+ errorsFound
159
+ ) {
160
+ const objType = createObjType(requestParams.objectType);
161
+ const targetIdentifiers = requestParams.targetIdentifiers;
162
+
163
+ const objectSchemas =
164
+ uploadUseCase.generateObjectSchemaForCombineFieldNames(
165
+ _izContext,
166
+ objectSchema
167
+ );
168
+ _izContext.logger.debug(
169
+ `[${process.env.iz_serviceTag}:<%- flowTag %>] objectSchemas:`,
170
+ objectSchemas
154
171
  );
155
172
 
156
- for (const [key, value] of Object.entries(identifiers)) {
157
- if (!requestParams.fieldNames.hasOwnProperty(key)) {
158
- requestParams.fieldNames[key] = value;
173
+ errorsFound.push(
174
+ ...(await createNodeLib.validateRequiredOnCreateLinks(
175
+ _izContext,
176
+ objType,
177
+ requestParams.relationships
178
+ ))
179
+ );
180
+ _izContext.logger.debug(
181
+ `[${process.env.iz_serviceTag}:<%- flowTag %>] errorsFound:`,
182
+ errorsFound
183
+ );
184
+
185
+ // ── Resolve fromRequiredOnCreate fields ──────────────────────────────────
186
+ if (objectSchemas.fieldNames) {
187
+ if (!requestParams.fieldNames) {
188
+ requestParams.fieldNames = {};
189
+ }
190
+ for (const [fieldName, val] of Object.entries(objectSchemas.fieldNames)) {
191
+ if (
192
+ val.fromRequiredOnCreate &&
193
+ requestParams.fieldNames[fieldName] === undefined
194
+ ) {
195
+ const fromConfig = val.fromRequiredOnCreate;
196
+ const sourceField = fromConfig.fieldName || fieldName;
197
+ if (fromConfig.belongToObjType === true && targetIdentifiers) {
198
+ let targetValue = targetIdentifiers[sourceField];
199
+ if (targetValue === undefined) {
200
+ targetValue = Object.values(targetIdentifiers)[0];
201
+ }
202
+ requestParams.fieldNames[fieldName] = targetValue;
203
+ } else if (
204
+ fromConfig.relType &&
205
+ requestParams.relationships?.length > 0
206
+ ) {
207
+ const matchedRel = requestParams.relationships.find(
208
+ (rel) =>
209
+ rel.relType?.serviceTag === fromConfig.relType.serviceTag &&
210
+ rel.relType?.relationshipTag === fromConfig.relType.relationshipTag
211
+ );
212
+ if (matchedRel?.targetIdentifiers?.[sourceField] !== undefined) {
213
+ requestParams.fieldNames[fieldName] =
214
+ matchedRel.targetIdentifiers[sourceField];
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ // ── Resolve identifiers ───────────────────────────────────────────────────
222
+ let errors;
223
+ const identifiers = objectSchemas.identifiers;
224
+ let optionalOnCreateIdentifiers;
225
+
226
+ if (
227
+ identifiers?.length === 1 &&
228
+ !identifiers[0].hasOwnProperty('fieldNames')
229
+ ) {
230
+ [requestParams, errors] = await createIdentifiersByUseCase(
231
+ _izContext,
232
+ requestParams,
233
+ objectSchemas,
234
+ identifiers[0],
235
+ optionalOnCreateIdentifiers
236
+ );
237
+ if (errors?.length > 0) {
238
+ errorsFound.push(...errors);
239
+ }
240
+ } else if (identifiers?.length > 1) {
241
+ for (const identifier of identifiers) {
242
+ [requestParams, errors] = await createIdentifiersByUseCase(
243
+ _izContext,
244
+ requestParams,
245
+ objectSchemas,
246
+ identifier,
247
+ optionalOnCreateIdentifiers
248
+ );
249
+ if (errors?.length > 0) {
250
+ errorsFound.push(...errors);
251
+ }
159
252
  }
160
253
  }
161
254
 
162
- _izContext.logger.debug(
163
- `[${process.env.iz_serviceTag}:<%- flowTag %>] Identifiers generated:`,
164
- identifiers
165
- );
255
+ return { objectSchemas };
166
256
  }
167
257
 
168
258
  /**
169
259
  * Validates request field names against schema definition
170
260
  * @param {Object} _izContext - Izara context object
171
- * @param {Object} objectSchemas - Combined object schema
172
261
  * @param {Object} requestParams - Incoming request parameters
262
+ * @param {Object} objectSchemas - Combined object schema
173
263
  * @param {Array<string>} errorsFound - Array to collect validation errors
174
264
  */
175
- export function validateFieldNames(_izContext, objectSchemas, requestParams, errorsFound) {
176
- const invalidFieldNames = [];
177
- for (const fieldName of Object.keys(requestParams.fieldNames || {})) {
178
- if (!objectSchemas.fieldNames.includes(fieldName)) {
179
- invalidFieldNames.push(fieldName);
265
+ export function validateFieldNames(
266
+ _izContext,
267
+ requestParams,
268
+ objectSchemas,
269
+ errorsFound
270
+ ) {
271
+ const listOfRequiredOnCreate = [];
272
+ const listOfOptionalOnCreate = [];
273
+
274
+ for (const [key, val] of Object.entries(objectSchemas?.fieldNames || {})) {
275
+ if (
276
+ val.randomOnCreate === true ||
277
+ val.requiredOnCreate === true ||
278
+ val.fromRequiredOnCreate ||
279
+ val.hashOnCreate?.length
280
+ ) {
281
+ listOfRequiredOnCreate.push(key);
282
+ }
283
+ if (val.requiredOnCreate === false && val.optionalOnCreate === true) {
284
+ listOfOptionalOnCreate.push(key);
180
285
  }
181
286
  }
182
287
 
183
- if (invalidFieldNames.length > 0) {
184
- errorsFound.push(
185
- `Invalid fieldName not match from Schema: ${invalidFieldNames.join(',')}`
186
- );
288
+ for (const fieldName of Object.keys(requestParams.fieldNames || {})) {
289
+ if (
290
+ !listOfRequiredOnCreate.includes(fieldName) &&
291
+ !listOfOptionalOnCreate.includes(fieldName)
292
+ ) {
293
+ errorsFound.push('[invalid] over fieldNames create object');
294
+ }
295
+ if (errorsFound.length > 0) {
296
+ _izContext.logger.debug(
297
+ `[${process.env.iz_serviceTag}:<%- flowTag %>] HaveError:`,
298
+ errorsFound
299
+ );
300
+ break;
301
+ }
187
302
  }
188
303
  }
189
304
 
@@ -641,60 +756,93 @@ export function resolveQuotaDeductionKeys(
641
756
  }
642
757
 
643
758
  /**
644
- * Forwards quota confirmation message to ConfirmUsage_In or CancelUsage_In
759
+ * Forwards quota confirmation or cancellation to RBAC via synchronous invoke
760
+ * and publishes final completion message to outCreateNodeComplete
645
761
  * @param {Object} _izContext - Izara context object
646
762
  * @param {Object} options - Message forwarding options
647
- * @returns {Promise<void>}
763
+ * @returns {Promise<Object>} Quota invocation result
648
764
  */
649
765
  export async function forwardToQuotaConfirmation(
650
766
  _izContext,
651
767
  {
652
768
  status,
653
- errorsFound,
769
+ errorsFound = [],
654
770
  quotaKeys,
655
771
  actionData,
656
772
  callingFlowConfig
657
773
  }
658
774
  ) {
659
- const nextTargetTopic =
660
- status === 'complete' && errorsFound.length === 0
661
- ? 'ConfirmUsage_In'
662
- : 'CancelUsage_In';
775
+ let quotaResult = null;
776
+ if (quotaKeys && (quotaKeys.limitTag || quotaKeys.flowTypeConcat)) {
777
+ let targetFunction = 'CancelUsageHdrInv';
778
+ if (status === 'complete' && errorsFound.length === 0) {
779
+ targetFunction = 'ConfirmUsageHdrInv';
780
+ }
663
781
 
664
- const forwardMessage =
665
- callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
666
- {
667
- ...quotaKeys,
668
- parentPassBackProperties: actionData
669
- },
670
- callingFlowSharedLib.addParentCallingFlowConfig(
671
- callingFlowConfig,
672
- callingFlowSharedLib.createCallingFlowConfig(
673
- await lambdaSharedLib.lambdaFunctionName(
674
- _izContext,
675
- 'CreateCompleteHdrSqs'
676
- ),
677
- {}
678
- )
679
- )
782
+ const functionName = await lambdaSharedLib.lambdaFunctionName(
783
+ _izContext,
784
+ targetFunction,
785
+ 'Rbac'
680
786
  );
681
787
 
682
- const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
683
- _izContext,
684
- nextTargetTopic,
685
- 'Rbac'
788
+ const forwardMessage = {
789
+ ...quotaKeys,
790
+ parentPassBackProperties: actionData,
791
+ callingFlowConfig
792
+ };
793
+
794
+ _izContext.logger.debug('[CreateLogicalLib] invoking RBAC quota sync', {
795
+ targetFunction,
796
+ functionName,
797
+ forwardMessage
798
+ });
799
+
800
+ try {
801
+ quotaResult = await lambda.invokeSync(
802
+ _izContext,
803
+ functionName,
804
+ forwardMessage
805
+ );
806
+ } catch (err) {
807
+ _izContext.logger.error(
808
+ `[CreateLogicalLib] error invoking ${targetFunction}:`,
809
+ err
810
+ );
811
+ if (targetFunction === 'ConfirmUsageHdrInv') {
812
+ try {
813
+ const cancelFunctionName = await lambdaSharedLib.lambdaFunctionName(
814
+ _izContext,
815
+ 'CancelUsageHdrInv',
816
+ 'Rbac'
817
+ );
818
+ await lambda.invokeSync(
819
+ _izContext,
820
+ cancelFunctionName,
821
+ forwardMessage
822
+ );
823
+ } catch (cancelErr) {
824
+ _izContext.logger.error(
825
+ '[CreateLogicalLib] error invoking CancelUsageHdrInv fallback:',
826
+ cancelErr
827
+ );
828
+ }
829
+ }
830
+ errorsFound.push(err.message || 'Quota confirmation failed');
831
+ if (actionData) {
832
+ actionData.status = 'error';
833
+ actionData.errorsFound = errorsFound;
834
+ }
835
+ }
836
+ }
837
+
838
+ const { message, messageAttributes } = buildCompletionPayload(
839
+ callingFlowConfig,
840
+ actionData
686
841
  );
687
842
 
688
- _izContext.logger.debug('[CreateNodeCallback] forwarding to RBAC', {
689
- nextTargetTopic,
690
- TopicArn: topicArn,
691
- forwardMessage
692
- });
843
+ await publishCompletionMessage(_izContext, message, messageAttributes);
693
844
 
694
- await sns.publishAsync(_izContext, {
695
- Message: JSON.stringify(forwardMessage),
696
- TopicArn: topicArn
697
- });
845
+ return quotaResult;
698
846
  }
699
847
 
700
848
  // ── 4. CreateComplete_Main Helpers ───────────────────────────────────────────
@@ -1344,6 +1492,7 @@ export async function <%- flowTag %>LogicalLib(
1344
1492
  }
1345
1493
  );
1346
1494
 
1495
+ const fieldsForHook = { ...objInstanceFullForDynamoDb.fields };
1347
1496
  objInstanceFullForDynamoDb.fields = {};
1348
1497
 
1349
1498
  // Auto-provision owner TargetRole for DynamoDB-only
@@ -1352,6 +1501,111 @@ export async function <%- flowTag %>LogicalLib(
1352
1501
  identifiersForCreateData,
1353
1502
  requestParams
1354
1503
  });
1504
+
1505
+ <%_ if (plan?.hasAfterHookSync) { _%>
1506
+ // ── Synchronous After Hooks ──
1507
+ <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'lambdaSyncInv').forEach(hook => { _%>
1508
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
1509
+ if (objType.objectType === '<%= hook.targetObjectTypes[0] %>') {
1510
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
1511
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(objType.objectType)) {
1512
+ <%_ } _%>
1513
+ try {
1514
+ let hookPayload_<%= hook.flowTag %> = {
1515
+ objType,
1516
+ objInstanceFull: {
1517
+ identifiers: identifiersForCreateData,
1518
+ fields: fieldsForHook
1519
+ },
1520
+ relationships: requestParams.relationships || [],
1521
+ targetIdentifiers: requestParams.targetIdentifiers,
1522
+ submittedByUserId: requestParams.submittedByUserId
1523
+ };
1524
+ //(<beforeSendHook_<%= hook.flowTag %>>)
1525
+ //(</beforeSendHook_<%= hook.flowTag %>>)
1526
+ const hookResult = await lambda.invokeSync(
1527
+ _izContext,
1528
+ await lambdaSharedLib.lambdaFunctionName(
1529
+ _izContext,
1530
+ '<%= hook.flowTag %>'
1531
+ ),
1532
+ {
1533
+ requestParams: hookPayload_<%= hook.flowTag %>,
1534
+ callingFlowConfig
1535
+ }
1536
+ );
1537
+ if (hookResult?.errorsFound?.length > 0 || hookResult?.status === 'error') {
1538
+ errorsFound.push(...(hookResult.errorsFound || ['Hook <%= hook.flowTag %> failed']));
1539
+ }
1540
+ } catch (hookErr) {
1541
+ _izContext.logger.error('[<%- flowTag %>LogicalLib] Sync hook <%= hook.flowTag %> error:', hookErr);
1542
+ errorsFound.push(hookErr.message || 'Hook <%= hook.flowTag %> failed');
1543
+ }
1544
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
1545
+ }
1546
+ <%_ } _%>
1547
+ <%_ }) _%>
1548
+ <%_ } _%>
1549
+
1550
+ <%_ if (plan?.hasAfterHookAsync) { _%>
1551
+ // ── Asynchronous After Hooks ──
1552
+ <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'ownTopic').forEach(hook => { _%>
1553
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
1554
+ if (objType.objectType === '<%= hook.targetObjectTypes[0] %>') {
1555
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
1556
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(objType.objectType)) {
1557
+ <%_ } _%>
1558
+ let hookPayload_<%= hook.flowTag %> = {
1559
+ objType,
1560
+ objInstanceFull: {
1561
+ identifiers: identifiersForCreateData,
1562
+ fields: fieldsForHook
1563
+ },
1564
+ relationships: requestParams.relationships || [],
1565
+ targetIdentifiers: requestParams.targetIdentifiers,
1566
+ submittedByUserId: requestParams.submittedByUserId
1567
+ };
1568
+ //(<beforeSendHook_<%= hook.flowTag %>>)
1569
+ //(</beforeSendHook_<%= hook.flowTag %>>)
1570
+ const hookMsg_<%= hook.flowTag %> = callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
1571
+ hookPayload_<%= hook.flowTag %>,
1572
+ callingFlowSharedLib.addParentCallingFlowConfig(
1573
+ callingFlowConfig,
1574
+ callingFlowSharedLib.createCallingFlowConfig(
1575
+ await lambdaSharedLib.lambdaFunctionName(
1576
+ _izContext,
1577
+ '<%- flowTag %>AfterHookCallbackHdrSqs'
1578
+ ),
1579
+ {
1580
+ actionData: {
1581
+ objType,
1582
+ objInstanceFull: {
1583
+ identifiers: identifiersForCreateData,
1584
+ fields: fieldsForHook
1585
+ },
1586
+ relationships: requestParams.relationships || [],
1587
+ targetIdentifiers: requestParams.targetIdentifiers,
1588
+ status: 'complete',
1589
+ errorsFound
1590
+ }
1591
+ }
1592
+ )
1593
+ )
1594
+ );
1595
+
1596
+ await sns.publishAsync(_izContext, {
1597
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
1598
+ _izContext,
1599
+ '<%= hook.flowTag %>_In'
1600
+ ),
1601
+ Message: JSON.stringify(hookMsg_<%= hook.flowTag %>),
1602
+ MessageAttributes: sns.createStringMessageAttributes(_izContext, {})
1603
+ });
1604
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
1605
+ }
1606
+ <%_ } _%>
1607
+ <%_ }) _%>
1608
+ <%_ } _%>
1355
1609
  }
1356
1610
 
1357
1611
  //(<afterCreateRecordDynamo>)
@@ -1505,11 +1759,13 @@ export async function <%- flowTag %>LogicalLib(
1505
1759
 
1506
1760
  // 7. Handle DynamoDB-only Complete Path
1507
1761
  if (allAwaitingStepsId.length === 0 && listOfObjectForCreates.length === 0) {
1762
+ <%_ if (!plan?.hasAfterHookAsync) { _%>
1508
1763
  await handleDynamoDbOnlyComplete(_izContext, {
1509
1764
  objType,
1510
1765
  objInstanceFullForDynamoDb,
1511
1766
  callingFlowConfig
1512
1767
  });
1768
+ <%_ } _%>
1513
1769
  }
1514
1770
 
1515
1771
  return {
@@ -171,6 +171,48 @@ export default async function CreateNodeCallbackMain(
171
171
  node,
172
172
  submittedByUserId
173
173
  });
174
+
175
+ <%_ if (plan?.hasAfterHookSync) { _%>
176
+ // ── Synchronous After Hooks ──
177
+ <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'lambdaSyncInv').forEach(hook => { _%>
178
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
179
+ if (returnValue.requestParams?.objType?.objectType === '<%= hook.targetObjectTypes[0] %>') {
180
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
181
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(returnValue.requestParams?.objType?.objectType)) {
182
+ <%_ } _%>
183
+ try {
184
+ let hookPayload_<%= hook.flowTag %> = {
185
+ objType: returnValue.requestParams.objType,
186
+ objInstanceFull: node.objInstanceFull,
187
+ relationships: node.relationships || [],
188
+ targetIdentifiers: requestParams.targetIdentifiers,
189
+ submittedByUserId
190
+ };
191
+ //(<beforeSendHook_<%= hook.flowTag %>>)
192
+ //(</beforeSendHook_<%= hook.flowTag %>>)
193
+ const hookResult = await lambda.invokeSync(
194
+ _izContext,
195
+ await lambdaSharedLib.lambdaFunctionName(
196
+ _izContext,
197
+ '<%= hook.flowTag %>'
198
+ ),
199
+ {
200
+ requestParams: hookPayload_<%= hook.flowTag %>,
201
+ callingFlowConfig
202
+ }
203
+ );
204
+ if (hookResult?.errorsFound?.length > 0 || hookResult?.status === 'error') {
205
+ errorsFound.push(...(hookResult.errorsFound || ['Hook <%= hook.flowTag %> failed']));
206
+ }
207
+ } catch (hookErr) {
208
+ _izContext.logger.error('[CreateNodeCallback] Sync hook <%= hook.flowTag %> error:', hookErr);
209
+ errorsFound.push(hookErr.message || 'Hook <%= hook.flowTag %> failed');
210
+ }
211
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
212
+ }
213
+ <%_ } _%>
214
+ <%_ }) _%>
215
+ <%_ } _%>
174
216
  }
175
217
 
176
218
  const callingFlowConfig =
@@ -198,6 +240,53 @@ export default async function CreateNodeCallbackMain(
198
240
  _izContext
199
241
  );
200
242
 
243
+ <%_ if (plan?.hasAfterHookAsync) { _%>
244
+ // ── Asynchronous After Hooks ──
245
+ <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'ownTopic').forEach(hook => { _%>
246
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
247
+ if (returnValue.requestParams?.objType?.objectType === '<%= hook.targetObjectTypes[0] %>') {
248
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
249
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(returnValue.requestParams?.objType?.objectType)) {
250
+ <%_ } _%>
251
+ let hookPayload_<%= hook.flowTag %> = {
252
+ objType: returnValue.requestParams.objType,
253
+ objInstanceFull: node.objInstanceFull,
254
+ relationships: node.relationships || [],
255
+ targetIdentifiers: requestParams.targetIdentifiers,
256
+ submittedByUserId
257
+ };
258
+ //(<beforeSendHook_<%= hook.flowTag %>>)
259
+ //(</beforeSendHook_<%= hook.flowTag %>>)
260
+ const hookMsg_<%= hook.flowTag %> = callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
261
+ hookPayload_<%= hook.flowTag %>,
262
+ callingFlowSharedLib.addParentCallingFlowConfig(
263
+ callingFlowConfig,
264
+ callingFlowSharedLib.createCallingFlowConfig(
265
+ await lambdaSharedLib.lambdaFunctionName(
266
+ _izContext,
267
+ 'CreateAfterHookCallbackHdrSqs'
268
+ ),
269
+ {
270
+ quotaKeys,
271
+ actionData
272
+ }
273
+ )
274
+ )
275
+ );
276
+
277
+ await sns.publishAsync(_izContext, {
278
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
279
+ _izContext,
280
+ '<%= hook.flowTag %>_In'
281
+ ),
282
+ Message: JSON.stringify(hookMsg_<%= hook.flowTag %>),
283
+ MessageAttributes: sns.createStringMessageAttributes(_izContext, {})
284
+ });
285
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
286
+ }
287
+ <%_ } _%>
288
+ <%_ }) _%>
289
+ <%_ } else { _%>
201
290
  await forwardToQuotaConfirmation(_izContext, {
202
291
  status,
203
292
  errorsFound,
@@ -205,6 +294,7 @@ export default async function CreateNodeCallbackMain(
205
294
  actionData,
206
295
  callingFlowConfig
207
296
  });
297
+ <%_ } _%>
208
298
  }
209
299
 
210
300
  await asyncFlowSharedLib.removeAwaitingMultipleStep(