@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
@@ -167,18 +167,36 @@ export default async function CreateNodeCallbackMain(
167
167
  _izContext.correlationIds?.get(coreConsts.SUBMITTED_BY_USER_ID) ||
168
168
  _izContext.correlationIds?.get('submittedByUserId');
169
169
 
170
+ <%_ const rootRbacObjs = (objects || []).filter(obj => {
171
+ const hasSelfRbac = Array.isArray(obj?.addOnDataStructure) && obj.addOnDataStructure.some(addOn => addOn?.type === 'rbac');
172
+ return hasSelfRbac && !obj?.belongTo && obj?.objectType !== 'user' && obj?.generatedBy !== 'systemNoUser';
173
+ }); _%>
174
+ <%_ if (rootRbacObjs.length > 0) { _%>
170
175
  await autoProvisionOwnerTargetRole(_izContext, {
171
176
  node,
172
- submittedByUserId
177
+ submittedByUserId,
178
+ objType: returnValue?.requestParams?.objType
173
179
  });
180
+ <%_ } _%>
174
181
 
175
182
  <%_ if (plan?.hasAfterHookSync) { _%>
176
183
  // ── Synchronous After Hooks ──
177
184
  <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'lambdaSyncInv').forEach(hook => { _%>
178
- <%_ if (hook.objType?.objectType) { _%>
179
- if (returnValue.requestParams?.objType?.objectType === '<%= hook.objType.objectType %>') {
185
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
186
+ if (returnValue.requestParams?.objType?.objectType === '<%= hook.targetObjectTypes[0] %>') {
187
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
188
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(returnValue.requestParams?.objType?.objectType)) {
180
189
  <%_ } _%>
181
190
  try {
191
+ let hookPayload_<%= hook.flowTag %> = {
192
+ objType: returnValue.requestParams.objType,
193
+ objInstanceFull: node.objInstanceFull,
194
+ relationships: node.relationships || [],
195
+ targetIdentifiers: requestParams.targetIdentifiers,
196
+ submittedByUserId
197
+ };
198
+ //(<beforeSendHook_<%= hook.flowTag %>>)
199
+ //(</beforeSendHook_<%= hook.flowTag %>>)
182
200
  const hookResult = await lambda.invokeSync(
183
201
  _izContext,
184
202
  await lambdaSharedLib.lambdaFunctionName(
@@ -186,13 +204,7 @@ export default async function CreateNodeCallbackMain(
186
204
  '<%= hook.flowTag %>'
187
205
  ),
188
206
  {
189
- requestParams: {
190
- objType: returnValue.requestParams.objType,
191
- objInstanceFull: node.objInstanceFull,
192
- relationships: node.relationships || [],
193
- targetIdentifiers: requestParams.targetIdentifiers,
194
- submittedByUserId
195
- },
207
+ requestParams: hookPayload_<%= hook.flowTag %>,
196
208
  callingFlowConfig
197
209
  }
198
210
  );
@@ -203,7 +215,7 @@ export default async function CreateNodeCallbackMain(
203
215
  _izContext.logger.error('[CreateNodeCallback] Sync hook <%= hook.flowTag %> error:', hookErr);
204
216
  errorsFound.push(hookErr.message || 'Hook <%= hook.flowTag %> failed');
205
217
  }
206
- <%_ if (hook.objType?.objectType) { _%>
218
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
207
219
  }
208
220
  <%_ } _%>
209
221
  <%_ }) _%>
@@ -238,17 +250,22 @@ export default async function CreateNodeCallbackMain(
238
250
  <%_ if (plan?.hasAfterHookAsync) { _%>
239
251
  // ── Asynchronous After Hooks ──
240
252
  <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'ownTopic').forEach(hook => { _%>
241
- <%_ if (hook.objType?.objectType) { _%>
242
- if (returnValue.requestParams?.objType?.objectType === '<%= hook.objType.objectType %>') {
253
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
254
+ if (returnValue.requestParams?.objType?.objectType === '<%= hook.targetObjectTypes[0] %>') {
255
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
256
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(returnValue.requestParams?.objType?.objectType)) {
243
257
  <%_ } _%>
258
+ let hookPayload_<%= hook.flowTag %> = {
259
+ objType: returnValue.requestParams.objType,
260
+ objInstanceFull: node.objInstanceFull,
261
+ relationships: node.relationships || [],
262
+ targetIdentifiers: requestParams.targetIdentifiers,
263
+ submittedByUserId
264
+ };
265
+ //(<beforeSendHook_<%= hook.flowTag %>>)
266
+ //(</beforeSendHook_<%= hook.flowTag %>>)
244
267
  const hookMsg_<%= hook.flowTag %> = callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
245
- {
246
- objType: returnValue.requestParams.objType,
247
- objInstanceFull: node.objInstanceFull,
248
- relationships: node.relationships || [],
249
- targetIdentifiers: requestParams.targetIdentifiers,
250
- submittedByUserId
251
- },
268
+ hookPayload_<%= hook.flowTag %>,
252
269
  callingFlowSharedLib.addParentCallingFlowConfig(
253
270
  callingFlowConfig,
254
271
  callingFlowSharedLib.createCallingFlowConfig(
@@ -272,7 +289,7 @@ export default async function CreateNodeCallbackMain(
272
289
  Message: JSON.stringify(hookMsg_<%= hook.flowTag %>),
273
290
  MessageAttributes: sns.createStringMessageAttributes(_izContext, {})
274
291
  });
275
- <%_ if (hook.objType?.objectType) { _%>
292
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
276
293
  }
277
294
  <%_ } _%>
278
295
  <%_ }) _%>
@@ -0,0 +1,118 @@
1
+ /*
2
+ Copyright (C) 2026 Sven Mason <http://izara.io>
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import asyncFlowSharedLib from '@izara_project/izara-core-library-asynchronous-flow';
19
+ import callingFlowSharedLib from '@izara_project/izara-core-library-calling-flow';
20
+ import snsSharedLib from '@izara_project/izara-core-library-sns';
21
+ import { sns } from '@izara_project/izara-core-library-external-request';
22
+ import { consts } from '@izara_project/izara-core-library-service-schemas';
23
+
24
+ //(<optionalRequire>)
25
+ //(</optionalRequire>)
26
+
27
+ const { TOPIC_NAME_GENERATE_CODE } = consts;
28
+
29
+ /**
30
+ * Handles callback after async AfterHook completion for Delete
31
+ * @param {Object} _izContext - Izara context object
32
+ * @param {Object} requestParams - Callback request parameters
33
+ * @param {Object} passBackProperties - Pass-back properties
34
+ * @returns {Promise<Object>} Result object
35
+ */
36
+ export default async function DeleteAfterHookCallbackMain(
37
+ _izContext,
38
+ requestParams,
39
+ passBackProperties
40
+ //(<additionalParams>)
41
+ //(</additionalParams>)
42
+ ) {
43
+ try {
44
+ _izContext.logger.debug('[DeleteAfterHookCallback] started', {
45
+ requestParams,
46
+ passBackProperties
47
+ });
48
+
49
+ const {
50
+ status,
51
+ errorsFound = [],
52
+ returnValue,
53
+ parentFlowId
54
+ } = requestParams;
55
+
56
+ const mergedErrors = Array.isArray(errorsFound) ? [...errorsFound] : [];
57
+ if (status === 'error' && mergedErrors.length === 0) {
58
+ mergedErrors.push('AfterHook execution reported error');
59
+ }
60
+
61
+ const callingFlowConfig =
62
+ passBackProperties?.callingFlowConfig ||
63
+ requestParams?.passBackProperties?.callingFlowConfig ||
64
+ passBackProperties;
65
+
66
+ const actionData =
67
+ passBackProperties?.actionData ||
68
+ requestParams?.passBackProperties?.actionData ||
69
+ {
70
+ objType: returnValue?.objType || requestParams?.objType,
71
+ identifiers:
72
+ returnValue?.identifiers || requestParams?.identifiers || {},
73
+ targetIdentifiers: requestParams?.targetIdentifiers,
74
+ parentFlowId: parentFlowId || returnValue?.parentFlowId,
75
+ status: mergedErrors.length > 0 ? 'error' : 'complete',
76
+ errorsFound: mergedErrors
77
+ };
78
+
79
+ const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
80
+ _izContext,
81
+ TOPIC_NAME_GENERATE_CODE.outDeleteNodeComplete
82
+ );
83
+
84
+ const completionPayload =
85
+ callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
86
+ callingFlowConfig,
87
+ {
88
+ ...actionData,
89
+ status: mergedErrors.length > 0 ? 'error' : 'complete',
90
+ errorsFound: mergedErrors
91
+ }
92
+ );
93
+
94
+ await sns.publishAsync(_izContext, {
95
+ TopicArn: topicArn,
96
+ Message: JSON.stringify(completionPayload),
97
+ MessageAttributes: sns.createStringMessageAttributes(
98
+ _izContext,
99
+ callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
100
+ callingFlowConfig,
101
+ {}
102
+ )
103
+ )
104
+ });
105
+
106
+ //(<afterProcessCallback>)
107
+ //(</afterProcessCallback>)
108
+
109
+ return {
110
+ returnValue,
111
+ status: mergedErrors.length > 0 ? 'error' : 'complete',
112
+ errorsFound: mergedErrors
113
+ };
114
+ } catch (error) {
115
+ _izContext.logger.error('[DeleteAfterHookCallback] error:', error);
116
+ throw error;
117
+ }
118
+ }
@@ -1,8 +1,12 @@
1
1
  import asyncFlowSharedLib from '@izara_project/izara-core-library-asynchronous-flow';
2
2
  import callingFlowSharedLib from '@izara_project/izara-core-library-calling-flow';
3
3
  import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
4
+ import lambdaSharedLib from '@izara_project/izara-core-library-lambda';
4
5
  import snsSharedLib from '@izara_project/izara-core-library-sns';
5
- import { sns } from '@izara_project/izara-core-library-external-request';
6
+ import {
7
+ sns,
8
+ lambda
9
+ } from '@izara_project/izara-core-library-external-request';
6
10
  import { consts } from '@izara_project/izara-core-library-service-schemas';
7
11
  import { NoRetryError } from '@izara_project/izara-core-library-core';
8
12
 
@@ -142,18 +146,127 @@ export default async function DeleteComplete(
142
146
  const callingFlowConfig =
143
147
  result?.additionalAttributes?.callingFlowConfig ||
144
148
  passBackProperties;
149
+ const actionData = {
150
+ objType: returnValue.requestParams.objType,
151
+ objInstanceFull: node.objInstanceFull,
152
+ identifiers:
153
+ node.objInstanceFull?.identifiers ||
154
+ requestParams?.targetIdentifiers ||
155
+ {},
156
+ relationships: node.relationships,
157
+ parentFlowId: parentFlowId || returnValue?.parentFlowId,
158
+ existsNode,
159
+ status,
160
+ errorsFound
161
+ };
162
+
163
+ <%_ if (plan?.hasAfterHookSync) { _%>
164
+ // ── Synchronous After Hooks ──
165
+ <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'lambdaSyncInv').forEach(hook => { _%>
166
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
167
+ if (returnValue.requestParams?.objType?.objectType === '<%= hook.targetObjectTypes[0] %>') {
168
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
169
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(returnValue.requestParams?.objType?.objectType)) {
170
+ <%_ } _%>
171
+ try {
172
+ let hookPayload_<%= hook.flowTag %> = {
173
+ objType: returnValue.requestParams.objType,
174
+ identifiers:
175
+ node.objInstanceFull?.identifiers ||
176
+ requestParams.targetIdentifiers ||
177
+ {},
178
+ objInstanceFull: node.objInstanceFull,
179
+ relationships: node.relationships || [],
180
+ targetIdentifiers: requestParams.targetIdentifiers
181
+ };
182
+ //(<beforeSendHook_<%= hook.flowTag %>>)
183
+ //(</beforeSendHook_<%= hook.flowTag %>>)
184
+ const hookResult = await lambda.invokeSync(
185
+ _izContext,
186
+ await lambdaSharedLib.lambdaFunctionName(
187
+ _izContext,
188
+ '<%= hook.flowTag %>'
189
+ ),
190
+ {
191
+ requestParams: hookPayload_<%= hook.flowTag %>,
192
+ callingFlowConfig
193
+ }
194
+ );
195
+ if (
196
+ hookResult?.errorsFound?.length > 0 ||
197
+ hookResult?.status === 'error'
198
+ ) {
199
+ errorsFound.push(
200
+ ...(hookResult.errorsFound || ['Hook <%= hook.flowTag %> failed'])
201
+ );
202
+ }
203
+ } catch (hookErr) {
204
+ _izContext.logger.error(
205
+ '[DeleteComplete] Sync hook <%= hook.flowTag %> error:',
206
+ hookErr
207
+ );
208
+ errorsFound.push(hookErr.message || 'Hook <%= hook.flowTag %> failed');
209
+ }
210
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
211
+ }
212
+ <%_ } _%>
213
+ <%_ }) _%>
214
+ <%_ } _%>
215
+
216
+ <%_ if (plan?.hasAfterHookAsync) { _%>
217
+ // ── Asynchronous After Hooks ──
218
+ <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'ownTopic').forEach(hook => { _%>
219
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
220
+ if (returnValue.requestParams?.objType?.objectType === '<%= hook.targetObjectTypes[0] %>') {
221
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
222
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(returnValue.requestParams?.objType?.objectType)) {
223
+ <%_ } _%>
224
+ let hookPayload_<%= hook.flowTag %> = {
225
+ objType: returnValue.requestParams.objType,
226
+ identifiers:
227
+ node.objInstanceFull?.identifiers ||
228
+ requestParams.targetIdentifiers ||
229
+ {},
230
+ objInstanceFull: node.objInstanceFull,
231
+ relationships: node.relationships || [],
232
+ targetIdentifiers: requestParams.targetIdentifiers
233
+ };
234
+ //(<beforeSendHook_<%= hook.flowTag %>>)
235
+ //(</beforeSendHook_<%= hook.flowTag %>>)
236
+ const hookMsg_<%= hook.flowTag %> =
237
+ callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
238
+ hookPayload_<%= hook.flowTag %>,
239
+ callingFlowSharedLib.addParentCallingFlowConfig(
240
+ callingFlowConfig,
241
+ callingFlowSharedLib.createCallingFlowConfig(
242
+ await lambdaSharedLib.lambdaFunctionName(
243
+ _izContext,
244
+ 'DeleteAfterHookCallbackHdrSqs'
245
+ ),
246
+ {
247
+ actionData
248
+ }
249
+ )
250
+ )
251
+ );
252
+
253
+ await sns.publishAsync(_izContext, {
254
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
255
+ _izContext,
256
+ '<%= hook.flowTag %>_In'
257
+ ),
258
+ Message: JSON.stringify(hookMsg_<%= hook.flowTag %>),
259
+ MessageAttributes: sns.createStringMessageAttributes(_izContext, {})
260
+ });
261
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
262
+ }
263
+ <%_ } _%>
264
+ <%_ }) _%>
265
+ <%_ } else { _%>
145
266
  const message =
146
267
  callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
147
268
  callingFlowConfig,
148
- {
149
- objType: returnValue.requestParams.objType,
150
- objInstanceFull: node.objInstanceFull,
151
- relationships: node.relationships,
152
- parentFlowId: parentFlowId || returnValue?.parentFlowId,
153
- existsNode,
154
- status,
155
- errorsFound
156
- }
269
+ actionData
157
270
  );
158
271
  const messageAttributes =
159
272
  callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
@@ -176,6 +289,7 @@ export default async function DeleteComplete(
176
289
  ),
177
290
  TopicArn: topicArn
178
291
  });
292
+ <%_ } _%>
179
293
  //(<afterSendMessage>)
180
294
  //(</afterSendMessage>)
181
295
  }
@@ -188,11 +188,15 @@ export default async function <%- flowTag %>(
188
188
 
189
189
  // ── Optional phase extension point ────────────────────────────────────────
190
190
  <%_
191
+ const allCrudObjectTypes = (objects || []).filter(isCrudObject).map(o => o.objectType);
191
192
  const beforeHooksByObjType = (plan?.beforeHooks || []).reduce((acc, hook) => {
192
- const key = hook?.objType?.objectType;
193
- if (!key) return acc;
194
- if (!acc[key]) acc[key] = [];
195
- acc[key].push(hook);
193
+ const targets = (hook?.targetObjectTypes && hook.targetObjectTypes.length > 0)
194
+ ? hook.targetObjectTypes
195
+ : allCrudObjectTypes;
196
+ for (const key of targets) {
197
+ if (!acc[key]) acc[key] = [];
198
+ acc[key].push(hook);
199
+ }
196
200
  return acc;
197
201
  }, {});
198
202
  _%>
@@ -206,6 +210,14 @@ export default async function <%- flowTag %>(
206
210
  const lambdaSyncInvResults = [];
207
211
  <%_ for (const config of configs.filter(c => c?.generatedCodeConfig?.invocationType === 'lambdaSyncInv' || c?.event?.includes('lambdaSyncInv'))) { _%>
208
212
  try {
213
+ let hookPayload_<%= config.flowTag %> = {
214
+ objType,
215
+ identifiers: requestParams.identifiers,
216
+ targetIdentifiers,
217
+ submittedByUserId: requestParams.submittedByUserId
218
+ };
219
+ //(<beforeSendHook_<%= config.flowTag %>>)
220
+ //(</beforeSendHook_<%= config.flowTag %>>)
209
221
  const returnValue = await lambda.invokeSync(
210
222
  _izContext,
211
223
  await lambdaSharedLib.lambdaFunctionName(
@@ -213,12 +225,7 @@ export default async function <%- flowTag %>(
213
225
  '<%= config.flowTag %>'
214
226
  ),
215
227
  {
216
- requestParams: {
217
- objType,
218
- identifiers: requestParams.identifiers,
219
- targetIdentifiers,
220
- submittedByUserId: requestParams.submittedByUserId
221
- },
228
+ requestParams: hookPayload_<%= config.flowTag %>,
222
229
  callingFlowConfig
223
230
  }
224
231
  );
@@ -260,14 +267,17 @@ export default async function <%- flowTag %>(
260
267
  <%_ if (hasOwnTopic) { _%>
261
268
  // ── ownTopic (async — dispatch to before-hook and await callback) ──────────
262
269
  <%_ configs.filter(c => c?.generatedCodeConfig?.invocationType === 'ownTopic' || c?.event?.includes('ownTopic')).forEach(config => { _%>
270
+ let hookPayload_<%= config.flowTag %> = {
271
+ objType,
272
+ identifiers: requestParams.identifiers,
273
+ targetIdentifiers,
274
+ submittedByUserId: requestParams.submittedByUserId
275
+ };
276
+ //(<beforeSendHook_<%= config.flowTag %>>)
277
+ //(</beforeSendHook_<%= config.flowTag %>>)
263
278
  const hookMsg_<%= config.flowTag %> =
264
279
  callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
265
- {
266
- objType,
267
- identifiers: requestParams.identifiers,
268
- targetIdentifiers,
269
- submittedByUserId: requestParams.submittedByUserId
270
- },
280
+ hookPayload_<%= config.flowTag %>,
271
281
  callingFlowSharedLib.addParentCallingFlowConfig(
272
282
  callingFlowConfig,
273
283
  callingFlowSharedLib.createCallingFlowConfig(
@@ -100,9 +100,7 @@ export async function resolveSchemaAndIdentity(
100
100
 
101
101
  requestParams.submittedByUserId = submittedByUserId;
102
102
  if (targetIdentifiers) {
103
- if (!targetIdentifiers.submittedByUserId && submittedByUserId) {
104
- targetIdentifiers.submittedByUserId = submittedByUserId;
105
- }
103
+ requestParams.targetIdentifiers = targetIdentifiers;
106
104
  }
107
105
 
108
106
  _izContext.logger.debug(
@@ -274,6 +272,111 @@ export async function <%- flowTag %>LogicalLib(
274
272
  }
275
273
 
276
274
  // ── Complete ──────────────────────────────────────────────────────────────
275
+ <%_ if (plan?.hasAfterHookSync) { _%>
276
+ // ── Synchronous After Hooks ──
277
+ <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'lambdaSyncInv').forEach(hook => { _%>
278
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
279
+ if (objType?.objectType === '<%= hook.targetObjectTypes[0] %>') {
280
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
281
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(objType?.objectType)) {
282
+ <%_ } _%>
283
+ try {
284
+ let hookPayload_<%= hook.flowTag %> = {
285
+ objType,
286
+ identifiers: requestParams.identifiers,
287
+ relationships: requestParams.relationships || [],
288
+ targetIdentifiers: requestParams.targetIdentifiers,
289
+ submittedByUserId: requestParams.submittedByUserId
290
+ };
291
+ //(<beforeSendHook_<%= hook.flowTag %>>)
292
+ //(</beforeSendHook_<%= hook.flowTag %>>)
293
+ const hookResult = await lambda.invokeSync(
294
+ _izContext,
295
+ await lambdaSharedLib.lambdaFunctionName(
296
+ _izContext,
297
+ '<%= hook.flowTag %>'
298
+ ),
299
+ {
300
+ requestParams: hookPayload_<%= hook.flowTag %>,
301
+ callingFlowConfig
302
+ }
303
+ );
304
+ if (
305
+ hookResult?.errorsFound?.length > 0 ||
306
+ hookResult?.status === 'error'
307
+ ) {
308
+ errorsFound.push(
309
+ ...(hookResult.errorsFound || ['Hook <%= hook.flowTag %> failed'])
310
+ );
311
+ }
312
+ } catch (hookErr) {
313
+ _izContext.logger.error(
314
+ '[<%- flowTag %>LogicalLib] Sync hook <%= hook.flowTag %> error:',
315
+ hookErr
316
+ );
317
+ errorsFound.push(hookErr.message || 'Hook <%= hook.flowTag %> failed');
318
+ }
319
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
320
+ }
321
+ <%_ } _%>
322
+ <%_ }) _%>
323
+ <%_ } _%>
324
+
325
+ <%_ if (plan?.hasAfterHookAsync) { _%>
326
+ // ── Asynchronous After Hooks ──
327
+ <%_ plan.afterHooks.filter(h => h?.generatedCodeConfig?.invocationType === 'ownTopic').forEach(hook => { _%>
328
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length === 1) { _%>
329
+ if (objType?.objectType === '<%= hook.targetObjectTypes[0] %>') {
330
+ <%_ } else if (hook.targetObjectTypes && hook.targetObjectTypes.length > 1) { _%>
331
+ if (<%- JSON.stringify(hook.targetObjectTypes) %>.includes(objType?.objectType)) {
332
+ <%_ } _%>
333
+ let hookPayload_<%= hook.flowTag %> = {
334
+ objType,
335
+ identifiers: requestParams.identifiers,
336
+ relationships: requestParams.relationships || [],
337
+ targetIdentifiers: requestParams.targetIdentifiers,
338
+ submittedByUserId: requestParams.submittedByUserId
339
+ };
340
+ //(<beforeSendHook_<%= hook.flowTag %>>)
341
+ //(</beforeSendHook_<%= hook.flowTag %>>)
342
+ const hookMsg_<%= hook.flowTag %> =
343
+ callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
344
+ hookPayload_<%= hook.flowTag %>,
345
+ callingFlowSharedLib.addParentCallingFlowConfig(
346
+ callingFlowConfig,
347
+ callingFlowSharedLib.createCallingFlowConfig(
348
+ await lambdaSharedLib.lambdaFunctionName(
349
+ _izContext,
350
+ 'DeleteAfterHookCallbackHdrSqs'
351
+ ),
352
+ {
353
+ actionData: {
354
+ objType,
355
+ identifiers: requestParams.identifiers,
356
+ relationships: requestParams.relationships || [],
357
+ targetIdentifiers: requestParams.targetIdentifiers,
358
+ parentFlowId: requestParams.parentFlowId,
359
+ status: 'complete',
360
+ errorsFound
361
+ }
362
+ }
363
+ )
364
+ )
365
+ );
366
+
367
+ await sns.publishAsync(_izContext, {
368
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
369
+ _izContext,
370
+ '<%= hook.flowTag %>_In'
371
+ ),
372
+ Message: JSON.stringify(hookMsg_<%= hook.flowTag %>),
373
+ MessageAttributes: sns.createStringMessageAttributes(_izContext, {})
374
+ });
375
+ <%_ if (hook.targetObjectTypes && hook.targetObjectTypes.length > 0) { _%>
376
+ }
377
+ <%_ } _%>
378
+ <%_ }) _%>
379
+ <%_ } else { _%>
277
380
  const outMessage =
278
381
  callingFlowSharedLib.addPassBackPropertiesToSnsResponseMessageObject(
279
382
  callingFlowConfig,
@@ -304,12 +407,18 @@ export async function <%- flowTag %>LogicalLib(
304
407
  )
305
408
  });
306
409
  }
410
+ <%_ } _%>
411
+
412
+ let resultStatus = 'complete';
413
+ if (errorsFound.length > 0) {
414
+ resultStatus = 'error';
415
+ }
307
416
 
308
417
  return {
309
418
  objType,
310
419
  identifiers: requestParams.identifiers,
311
420
  targetIdentifiers: requestParams.targetIdentifiers,
312
- status: 'complete',
421
+ status: resultStatus,
313
422
  errorsFound
314
423
  };
315
424
  } catch (err) {