@izara_project/izara-core-generate-service-code 1.0.84 → 1.0.86

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 (46) hide show
  1. package/package.json +1 -1
  2. package/src/compile/compileProject.js +1 -2
  3. package/src/compile/loadSchemas.js +29 -17
  4. package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +6 -0
  5. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +26 -6
  6. package/src/emit/crud/crudActionPlan.js +3 -1
  7. package/src/emit/crud/emitCrudApplicationArtifacts.js +47 -8
  8. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +136 -25
  9. package/src/emit/crud/templates/CreateConfirmComplete_Main.ejs +54 -0
  10. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +248 -181
  11. package/src/emit/crud/templates/CreateLogicalLib.ejs +1155 -293
  12. package/src/emit/crud/templates/CreateNodeCallback_Main.ejs +221 -0
  13. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +36 -4
  14. package/src/emit/crud/templates/FlowEndpointRbacCallback_Main.ejs +20 -120
  15. package/src/emit/crud/templates/FlowEndpoint_HdrWbs.ejs +76 -14
  16. package/src/emit/crud/templates/FlowMain_Wbs.ejs +31 -35
  17. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +35 -3
  18. package/src/emit/crud/templates/UpdateLogicalLib.ejs +754 -353
  19. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +5 -2
  20. package/src/emit/rbac/emitRbacApplicationArtifacts.js +102 -6
  21. package/src/emit/rbac/templates/CancelUsage_Main.ejs +82 -22
  22. package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +130 -14
  23. package/src/emit/rbac/templates/CreateTargetRole_Main.ejs +61 -12
  24. package/src/emit/rbac/templates/DeleteTargetRole_Main.ejs +96 -0
  25. package/src/emit/rbac/templates/GetMyPermissions_Main.ejs +106 -0
  26. package/src/emit/rbac/templates/GetTargetRole_Main.ejs +62 -0
  27. package/src/emit/rbac/templates/ReservedDynamicUsage_Main.ejs +30 -11
  28. package/src/emit/rbac/templates/UpdateTargetRole_Main.ejs +107 -0
  29. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +40 -15
  30. package/src/emit/rbac/templates/UserRoleMutation_Main.ejs +22 -7
  31. package/src/emit/resource/emitResourceArtifacts.js +52 -2
  32. package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +6 -2
  33. package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +2 -0
  34. package/src/emit/shared/templates/FlowHandler_HdrSqs.ejs +1 -1
  35. package/src/emit/system/templates/WebSocketConnect_Yaml.ejs +0 -1
  36. package/src/emit/systemText/emitSystemTextSchemas.js +5 -2
  37. package/src/generate.js +18 -12
  38. package/src/generateResources/IntTest/generateResources/generateResources.js +1 -1
  39. package/src/generateResources/IntTest/generateTests/generateTemplate.js +1 -1
  40. package/src/generateResources/IntTest/upload/uploadIntTest.js +8 -2
  41. package/test/createTargetRoleHandler.test.js +42 -1
  42. package/test/crudApplicationArtifacts.test.js +53 -0
  43. package/test/dynamicCrudFlowSchemas.test.js +16 -2
  44. package/test/dynamicRbacFlowSchemas.test.js +5 -2
  45. package/test/publicApi.test.js +18 -14
  46. package/test/resourceArtifacts.test.js +24 -0
@@ -61,11 +61,1089 @@ const PREFIX = {
61
61
  CREATE_OBJECT_EXTERNAL_TOPIC: 'CreateExternalTopicEndpoint'
62
62
  };
63
63
 
64
+ // ── 1. Create_Main Helpers ───────────────────────────────────────────────────
65
+
66
+ /**
67
+ * Loads ObjectSchema and resolves submittedByUserId and targetIdentifiers
68
+ * @param {Object} _izContext - Izara context object
69
+ * @param {Object} requestParams - Incoming request parameters
70
+ * @param {Array<string>} errorsFound - Array to collect validation errors
71
+ * @returns {Promise<{ objType: string, objectSchema: Object }>}
72
+ */
73
+ export async function resolveSchemaAndIdentity(_izContext, requestParams, errorsFound) {
74
+ const objType = createObjType(requestParams.objectType);
75
+ const objectSchema = await getObjectSchema.getObjSchemaS3WithHierarchy(
76
+ _izContext,
77
+ objType
78
+ );
79
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] objectSchema:`, objectSchema);
80
+
81
+ let submittedByUserId, targetIdentifiers;
82
+ if (
83
+ objectSchema.generatedBy === 'user' ||
84
+ objectSchema.generatedBy === 'systemWithUser'
85
+ ) {
86
+ submittedByUserId =
87
+ requestParams.submittedByUserId ||
88
+ _izContext.correlationIds.get(coreConsts.SUBMITTED_BY_USER_ID);
89
+
90
+ targetIdentifiers = requestParams.targetIdentifiers;
91
+
92
+ if (!targetIdentifiers && submittedByUserId) {
93
+ targetIdentifiers = { submittedByUserId };
94
+ }
95
+
96
+ if (!submittedByUserId) errorsFound.push('Not have submittedByUserId');
97
+ if (objectSchema.hasOwnProperty('belongTo') && !targetIdentifiers)
98
+ errorsFound.push('not have targetIdentifiers');
99
+
100
+ if (targetIdentifiers) {
101
+ if (!targetIdentifiers.submittedByUserId && submittedByUserId) {
102
+ targetIdentifiers.submittedByUserId = submittedByUserId;
103
+ }
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
+ }
124
+
125
+ requestParams.targetIdentifiers = targetIdentifiers;
126
+ }
127
+
128
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] Identity & Target Context Resolved:`, {
129
+ submittedByUserId: requestParams.submittedByUserId,
130
+ targetIdentifiers: requestParams.targetIdentifiers,
131
+ belongTo: objectSchema.belongTo,
132
+ generatedBy: objectSchema.generatedBy
133
+ });
134
+ } else if (objectSchema.generatedBy === 'searchResultGenerated') {
135
+ submittedByUserId = requestParams.submittedByUserId;
136
+ }
137
+
138
+ requestParams.submittedByUserId = submittedByUserId;
139
+ return { objType, objectSchema };
140
+ }
141
+
142
+ /**
143
+ * Resolves and generates primary identifiers for the object
144
+ * @param {Object} _izContext - Izara context object
145
+ * @param {Object} objectSchemas - Combined object schema
146
+ * @param {Object} requestParams - Incoming request parameters
147
+ * @returns {Promise<void>}
148
+ */
149
+ export async function resolveAndGenerateIdentifiers(_izContext, objectSchemas, requestParams) {
150
+ const identifiers = await createIdentifiersByUseCase(
151
+ _izContext,
152
+ objectSchemas,
153
+ requestParams.fieldNames
154
+ );
155
+
156
+ for (const [key, value] of Object.entries(identifiers)) {
157
+ if (!requestParams.fieldNames.hasOwnProperty(key)) {
158
+ requestParams.fieldNames[key] = value;
159
+ }
160
+ }
161
+
162
+ _izContext.logger.debug(
163
+ `[${process.env.iz_serviceTag}:<%- flowTag %>] Identifiers generated:`,
164
+ identifiers
165
+ );
166
+ }
167
+
168
+ /**
169
+ * Validates request field names against schema definition
170
+ * @param {Object} _izContext - Izara context object
171
+ * @param {Object} objectSchemas - Combined object schema
172
+ * @param {Object} requestParams - Incoming request parameters
173
+ * @param {Array<string>} errorsFound - Array to collect validation errors
174
+ */
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);
180
+ }
181
+ }
182
+
183
+ if (invalidFieldNames.length > 0) {
184
+ errorsFound.push(
185
+ `Invalid fieldName not match from Schema: ${invalidFieldNames.join(',')}`
186
+ );
187
+ }
188
+ }
189
+
190
+ // ── 2. CreateRbacCallback_Main Helpers ────────────────────────────────────────
191
+
192
+ /**
193
+ * Checks awaiting steps status and collects returned results from App and Target RBAC
194
+ * @param {Object} _izContext - Izara context object
195
+ * @param {Object} requestParams - Request parameters with parentFlowId
196
+ * @returns {Promise<{ isComplete: boolean, rbacResults?: Object, originalRequestParams?: Object, collectedErrors?: Array<string> }>}
197
+ */
198
+ export async function collectRbacAwaitingResults(_izContext, requestParams) {
199
+ const parts = requestParams.parentFlowId.split('_');
200
+ const rbacType = parts.pop();
201
+ const rbacPendingStepId = '<%- flowTag %>RbacCallback_' + parts.join('_');
202
+ const currentAwaitingStepId = requestParams.parentFlowId;
203
+
204
+ _izContext.logger.debug(
205
+ '[<%- flowTag %>RbacCallbackMain] Parsed RBAC pending details:',
206
+ {
207
+ rbacType,
208
+ rbacPendingStepId,
209
+ currentAwaitingStepId,
210
+ parentFlowId: requestParams.parentFlowId
211
+ }
212
+ );
213
+
214
+ const rbacAwaitingStepsResult =
215
+ await asyncFlowSharedLib.checkAllAwaitingStepsFinishedWithReturnParams(
216
+ _izContext,
217
+ rbacPendingStepId,
218
+ currentAwaitingStepId,
219
+ requestParams.errorsFound,
220
+ {
221
+ returnValue: requestParams.returnValue,
222
+ rbacType
223
+ }
224
+ );
225
+
226
+ _izContext.logger.debug(
227
+ '[<%- flowTag %>RbacCallbackMain] checkAllAwaitingStepsFinishedWithReturnParams result:',
228
+ {
229
+ rbacAwaitingStepsResult
230
+ }
231
+ );
232
+
233
+ const {
234
+ isComplete,
235
+ collectedReturnValueByAwaitingStepId,
236
+ collectedErrors: errorsFound = [],
237
+ additionalAttributes
238
+ } = rbacAwaitingStepsResult;
239
+
240
+ if (isComplete === false) {
241
+ _izContext.logger.debug(
242
+ '[<%- flowTag %>RbacCallbackMain] Awaiting steps still pending completion:',
243
+ {
244
+ rbacPendingStepId,
245
+ isComplete
246
+ }
247
+ );
248
+ return { isComplete: false };
249
+ }
250
+
251
+ _izContext.logger.debug(
252
+ '[<%- flowTag %>RbacCallbackMain] All awaiting steps completed — clearing awaiting steps:',
253
+ {
254
+ rbacPendingStepId
255
+ }
256
+ );
257
+
258
+ await asyncFlowSharedLib.clearAllAwaitingSteps(
259
+ _izContext,
260
+ rbacPendingStepId
261
+ );
262
+
263
+ const originalRequestParams = additionalAttributes?.requestParams || {};
264
+ const rbacResults = Object.fromEntries(
265
+ Object.entries(collectedReturnValueByAwaitingStepId || {}).map(
266
+ ([stepId, attrs]) => {
267
+ const type = stepId.split('_').pop();
268
+ return [type, attrs];
269
+ }
270
+ )
271
+ );
272
+
273
+ return {
274
+ isComplete: true,
275
+ rbacResults,
276
+ originalRequestParams,
277
+ collectedErrors: errorsFound
278
+ };
279
+ }
280
+
281
+ /**
282
+ * Asserts RBAC permission and publishes rejected response if unauthorized
283
+ * @param {Object} _izContext - Izara context object
284
+ * @param {Object} options - Validation options
285
+ * @returns {Promise<{ hasError: boolean, errorResult?: Object, rbacCallbackResults: Object }>}
286
+ */
287
+ export async function assertRbacPermission(_izContext, { rbacResults, originalRequestParams, errorsFound }) {
288
+ const rbacCallbackResults = {};
289
+ const hasError = Object.entries(rbacResults).some(([type, result]) => {
290
+ if (result && result.error) {
291
+ errorsFound.push(result.error);
292
+ return true;
293
+ }
294
+ rbacCallbackResults[type] = result;
295
+ return false;
296
+ });
297
+
298
+ const isAllowed = Object.values(rbacCallbackResults).some(
299
+ item => item?.returnValue?.permission === true
300
+ );
301
+
302
+ if (!isAllowed) {
303
+ errorsFound.push('Permission denied');
304
+ }
305
+
306
+ _izContext.logger.debug(
307
+ '[<%- flowTag %>RbacCallbackMain] Final rbacCallbackResults before calling <%- flowTag %>MainLogical:',
308
+ {
309
+ rbacCallbackResults,
310
+ isAllowed,
311
+ errorsFound
312
+ }
313
+ );
314
+
315
+ if (hasError || errorsFound.length > 0) {
316
+ const errorResult = {
317
+ errorsFound,
318
+ status: 'rejected',
319
+ requestParams: originalRequestParams
320
+ };
321
+
322
+ _izContext.logger.debug(
323
+ '[<%- flowTag %>RbacCallbackMain] RBAC check failed, publishing rejected status:',
324
+ errorResult
325
+ );
326
+
327
+ await sns.publishAsync(_izContext, {
328
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
329
+ _izContext,
330
+ '<%- upperCaseFlowTag %>_Out',
331
+ process.env.iz_serviceTag
332
+ ),
333
+ Message: JSON.stringify(errorResult)
334
+ });
335
+
336
+ return { hasError: true, errorResult, rbacCallbackResults };
337
+ }
338
+
339
+ return { hasError: false, rbacCallbackResults };
340
+ }
341
+
342
+ // ── 3. CreateNodeCallback_Main Helpers ────────────────────────────────────────
343
+
344
+ /**
345
+ * Saves DynamoDB records from listOfRecords
346
+ * @param {Object} _izContext - Izara context object
347
+ * @param {Array<Object>} listOfRecords - Records to persist
348
+ * @returns {Promise<void>}
349
+ */
350
+ export async function saveDynamoDbRecords(_izContext, listOfRecords) {
351
+ if (!Array.isArray(listOfRecords) || listOfRecords.length === 0) {
352
+ return;
353
+ }
354
+
355
+ await Promise.all(
356
+ listOfRecords.flatMap((records) =>
357
+ Object.values(records).map(async (record) =>
358
+ dynamodbSharedLib.putItem(
359
+ _izContext,
360
+ await dynamodbSharedLib.tableName(
361
+ _izContext,
362
+ record.tableName,
363
+ record.serviceTag
364
+ ),
365
+ {
366
+ ...record.objInstanceFull.identifiers,
367
+ ...record.objInstanceFull.fields
368
+ }
369
+ )
370
+ )
371
+ )
372
+ );
373
+ }
374
+
375
+ /**
376
+ * Auto-provisions owner TargetRole for Root RBAC object after Graph and DynamoDB write
377
+ * @param {Object} _izContext - Izara context object
378
+ * @param {Object} options - Target node and submitter details
379
+ * @returns {Promise<void>}
380
+ */
381
+ export async function autoProvisionOwnerTargetRole(_izContext, { node, submittedByUserId }) {
382
+ try {
383
+ if (!submittedByUserId || !node?.objInstanceFull?.identifiers) {
384
+ return;
385
+ }
386
+
387
+ const identifiers = node.objInstanceFull.identifiers;
388
+ const targetIdentKeys = Object.entries(identifiers)
389
+ .filter(([k]) => k !== 'submittedByUserId')
390
+ .map(([k, v]) => `${k}#${v}`)
391
+ .join('|');
392
+
393
+ const targetIdentifiersConcat =
394
+ targetIdentKeys ||
395
+ (identifiers[Object.keys(identifiers)[0]]
396
+ ? `${Object.keys(identifiers)[0]}#${identifiers[Object.keys(identifiers)[0]]}`
397
+ : '');
398
+
399
+ if (!targetIdentifiersConcat) {
400
+ return;
401
+ }
402
+
403
+ const roleIdKey = `${targetIdentifiersConcat}_owner`;
404
+ _izContext.logger.debug(
405
+ '[CreateNodeCallback] Auto-provisioning owner TargetRole:',
406
+ {
407
+ identifiersConcat: targetIdentifiersConcat,
408
+ roleIdKey,
409
+ submittedByUserId,
410
+ roleName: 'owner'
411
+ }
412
+ );
413
+
414
+ await dynamodbSharedLib.putItem(
415
+ _izContext,
416
+ dynamodbSharedLib.tableName(_izContext, 'TargetRoles'),
417
+ {
418
+ identifiersConcat: targetIdentifiersConcat,
419
+ roleIdKey,
420
+ submittedByUserId,
421
+ roleName: 'owner',
422
+ assignedAt: new Date().toISOString()
423
+ }
424
+ );
425
+
426
+ await dynamodbSharedLib.transactWriteItems(_izContext, [
427
+ {
428
+ action: 'put',
429
+ params: {
430
+ tableName: dynamodbSharedLib.tableName(
431
+ _izContext,
432
+ 'RoleUsers'
433
+ ),
434
+ attributes: {
435
+ roleIdKey,
436
+ userId: submittedByUserId,
437
+ submittedByUserId
438
+ }
439
+ }
440
+ },
441
+ {
442
+ action: 'put',
443
+ params: {
444
+ tableName: dynamodbSharedLib.tableName(
445
+ _izContext,
446
+ 'UserRoles'
447
+ ),
448
+ attributes: {
449
+ userId: submittedByUserId,
450
+ roleIdKey,
451
+ submittedByUserId
452
+ }
453
+ }
454
+ },
455
+ {
456
+ action: 'put',
457
+ params: {
458
+ tableName: dynamodbSharedLib.tableName(
459
+ _izContext,
460
+ 'RolePermissions'
461
+ ),
462
+ attributes: {
463
+ roleIdKey,
464
+ flowTypeConcat: `${process.env.iz_serviceTag}_Update`,
465
+ permission: true,
466
+ allow: 'accept',
467
+ parameterGroups: {},
468
+ submittedByUserId
469
+ }
470
+ }
471
+ },
472
+ {
473
+ action: 'put',
474
+ params: {
475
+ tableName: dynamodbSharedLib.tableName(
476
+ _izContext,
477
+ 'RolePermissions'
478
+ ),
479
+ attributes: {
480
+ roleIdKey,
481
+ flowTypeConcat: `${process.env.iz_serviceTag}_Delete`,
482
+ permission: true,
483
+ allow: 'accept',
484
+ parameterGroups: {},
485
+ submittedByUserId
486
+ }
487
+ }
488
+ }
489
+ ]);
490
+ } catch (roleError) {
491
+ _izContext.logger.error(
492
+ '[CreateNodeCallback] Failed to auto-provision owner TargetRole:',
493
+ { roleError }
494
+ );
495
+ }
496
+ }
497
+
498
+ /**
499
+ * Resolves keys and identifiers for Quota Deduction / Confirmation
500
+ * @param {Object} requestParams - Request parameters
501
+ * @param {Object} passBackProperties - Pass back properties
502
+ * @param {Object} returnValue - Return value from graph service
503
+ * @param {Object} callingFlowConfig - Calling flow configuration
504
+ * @param {Object} _izContext - Izara context object
505
+ * @returns {Object} Quota deduction keys
506
+ */
507
+ export function resolveQuotaDeductionKeys(
508
+ requestParams,
509
+ passBackProperties,
510
+ returnValue,
511
+ callingFlowConfig,
512
+ _izContext
513
+ ) {
514
+ const targetIdentifiers =
515
+ requestParams?.passBackProperties?.targetIdentifiers ||
516
+ passBackProperties?.passBackProperties?.targetIdentifiers ||
517
+ passBackProperties?.targetIdentifiers ||
518
+ returnValue?.requestParams?.targetIdentifiers;
519
+
520
+ const submittedByUserId =
521
+ returnValue?.requestParams?.submittedByUserId ||
522
+ targetIdentifiers?.submittedByUserId ||
523
+ requestParams?.submittedByUserId ||
524
+ requestParams?.passBackProperties?.submittedByUserId ||
525
+ passBackProperties?.passBackProperties?.submittedByUserId ||
526
+ passBackProperties?.submittedByUserId ||
527
+ _izContext.correlationIds?.get(coreConsts.SUBMITTED_BY_USER_ID) ||
528
+ _izContext.correlationIds?.get('submittedByUserId') ||
529
+ _izContext.correlationIds?.get('userId');
530
+
531
+ let identifiersConcat =
532
+ requestParams?.passBackProperties?.identifiersConcat ||
533
+ passBackProperties?.passBackProperties?.identifiersConcat ||
534
+ passBackProperties?.identifiersConcat ||
535
+ null;
536
+
537
+ if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
538
+ if (targetIdentifiers.identifierConcat) {
539
+ identifiersConcat = targetIdentifiers.identifierConcat;
540
+ } else {
541
+ const sortedKeys = Object.keys(targetIdentifiers).sort();
542
+ const parts = [];
543
+ for (const key of sortedKeys) {
544
+ const value = targetIdentifiers[key];
545
+ if (
546
+ key !== 'submittedByUserId' &&
547
+ key !== 'identifierConcat' &&
548
+ value !== undefined &&
549
+ value !== null
550
+ ) {
551
+ parts.push(`${key}#${value}`);
552
+ }
553
+ }
554
+ if (parts.length > 0) {
555
+ identifiersConcat = parts.join('|');
556
+ }
557
+ }
558
+ }
559
+ if (!identifiersConcat) {
560
+ identifiersConcat = submittedByUserId;
561
+ }
562
+
563
+ const limitTag =
564
+ requestParams?.passBackProperties?.limitTag ||
565
+ passBackProperties?.passBackProperties?.limitTag ||
566
+ passBackProperties?.limitTag ||
567
+ callingFlowConfig?.limitTag ||
568
+ requestParams?.passBackProperties?.flowTypeConcat ||
569
+ passBackProperties?.passBackProperties?.flowTypeConcat ||
570
+ passBackProperties?.flowTypeConcat ||
571
+ callingFlowConfig?.flowTypeConcat ||
572
+ `${process.env.iz_serviceTag}_<%- flowTag %>`;
573
+
574
+ const flowTypeConcat = limitTag;
575
+
576
+ const uniqueIdentify =
577
+ requestParams?.passBackProperties?.uniqueIdentify ||
578
+ passBackProperties?.passBackProperties?.uniqueIdentify ||
579
+ passBackProperties?.uniqueIdentify ||
580
+ callingFlowConfig?.uniqueIdentify ||
581
+ _izContext.uniqueRequestId;
582
+
583
+ const usedTimestampId = uniqueIdentify;
584
+
585
+ const runningTotalReservedId =
586
+ requestParams?.passBackProperties?.runningTotalReservedId ||
587
+ passBackProperties?.passBackProperties?.runningTotalReservedId ||
588
+ passBackProperties?.runningTotalReservedId ||
589
+ callingFlowConfig?.runningTotalReservedId ||
590
+ (identifiersConcat && limitTag
591
+ ? `${identifiersConcat}_${limitTag}`
592
+ : null);
593
+
594
+ const rbacType =
595
+ requestParams?.passBackProperties?.rbacType ||
596
+ passBackProperties?.passBackProperties?.rbacType ||
597
+ passBackProperties?.rbacType ||
598
+ callingFlowConfig?.rbacType;
599
+
600
+ return {
601
+ limitTag,
602
+ flowTypeConcat,
603
+ uniqueIdentify,
604
+ usedTimestampId,
605
+ runningTotalReservedId,
606
+ identifiersConcat,
607
+ targetIdentifiers,
608
+ rbacType
609
+ };
610
+ }
611
+
612
+ /**
613
+ * Forwards quota confirmation message to ConfirmUsage_In or CancelUsage_In
614
+ * @param {Object} _izContext - Izara context object
615
+ * @param {Object} options - Message forwarding options
616
+ * @returns {Promise<void>}
617
+ */
618
+ export async function forwardToQuotaConfirmation(
619
+ _izContext,
620
+ {
621
+ status,
622
+ errorsFound,
623
+ quotaKeys,
624
+ actionData,
625
+ callingFlowConfig
626
+ }
627
+ ) {
628
+ const nextTargetTopic =
629
+ status === 'complete' && errorsFound.length === 0
630
+ ? 'ConfirmUsage_In'
631
+ : 'CancelUsage_In';
632
+
633
+ const forwardMessage =
634
+ callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
635
+ {
636
+ ...quotaKeys,
637
+ parentPassBackProperties: actionData
638
+ },
639
+ callingFlowSharedLib.addParentCallingFlowConfig(
640
+ callingFlowConfig,
641
+ callingFlowSharedLib.createCallingFlowConfig(
642
+ await lambdaSharedLib.lambdaFunctionName(
643
+ _izContext,
644
+ 'CreateCompleteHdrSqs'
645
+ ),
646
+ {}
647
+ )
648
+ )
649
+ );
650
+
651
+ const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
652
+ _izContext,
653
+ nextTargetTopic,
654
+ 'Rbac'
655
+ );
656
+
657
+ _izContext.logger.debug('[CreateNodeCallback] forwarding to RBAC', {
658
+ nextTargetTopic,
659
+ TopicArn: topicArn,
660
+ forwardMessage
661
+ });
662
+
663
+ await sns.publishAsync(_izContext, {
664
+ Message: JSON.stringify(forwardMessage),
665
+ TopicArn: topicArn
666
+ });
667
+ }
668
+
669
+ // ── 4. CreateComplete_Main Helpers ───────────────────────────────────────────
670
+
671
+ /**
672
+ * Builds response payload and message attributes for Calling Flow
673
+ * @param {Object} callingFlowConfig - Calling flow configuration
674
+ * @param {Object} parentPassBack - Parent pass-back data
675
+ * @returns {{ message: Object, messageAttributes: Object }}
676
+ */
677
+ export function buildCompletionPayload(callingFlowConfig, parentPassBack) {
678
+ const message =
679
+ callingFlowSharedLib.addParentPassBackPropertiesToSnsResponseMessageObject(
680
+ callingFlowConfig,
681
+ parentPassBack
682
+ );
683
+
684
+ const messageAttributes =
685
+ callingFlowSharedLib.addParentPassBackCallingFlowToSnsResponseMessageAttributes(
686
+ callingFlowConfig,
687
+ {}
688
+ );
689
+
690
+ return { message, messageAttributes };
691
+ }
692
+
693
+ /**
694
+ * Publishes completion response message to outCreateNodeComplete
695
+ * @param {Object} _izContext - Izara context object
696
+ * @param {Object} message - SNS message payload
697
+ * @param {Object} messageAttributes - SNS message attributes
698
+ * @returns {Promise<void>}
699
+ */
700
+ export async function publishCompletionMessage(_izContext, message, messageAttributes) {
701
+ const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
702
+ _izContext,
703
+ TOPIC_NAME_GENERATE_CODE.outCreateNodeComplete
704
+ );
705
+
706
+ _izContext.logger.debug('[CreateComplete] publishing to SNS', {
707
+ TopicArn: topicArn,
708
+ message
709
+ });
710
+
711
+ await sns.publishAsync(_izContext, {
712
+ Message: JSON.stringify(message),
713
+ MessageAttributes: sns.createStringMessageAttributes(
714
+ _izContext,
715
+ messageAttributes
716
+ ),
717
+ TopicArn: topicArn
718
+ });
719
+ }
720
+
721
+ // ── 5. CreateLogicalLib Internal Helpers ──────────────────────────────────────
722
+
723
+ /**
724
+ * Resolves and combines object schemas from S3/hierarchy
725
+ * @param {Object} _izContext - Izara context object
726
+ * @param {string} objectType - Object type identifier
727
+ * @returns {Promise<{ objType: Object, objectSchema: Object, objectSchemas: Object, parentObject?: Object }>}
728
+ */
729
+ export async function resolveObjectSchemas(_izContext, objectType) {
730
+ const objType = createObjType(objectType);
731
+ const objectSchema = await getObjectSchema.getObjSchemaS3WithHierarchy(
732
+ _izContext,
733
+ objType
734
+ );
735
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] objectSchema:`, objectSchema);
736
+
737
+ const objectSchemas =
738
+ uploadUseCase.generateObjectSchemaForCombineFieldNames(
739
+ _izContext,
740
+ objectSchema
741
+ );
742
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] objectSchemas:`, objectSchemas);
743
+
744
+ let parentObject;
745
+ if (objectSchemas.hasOwnProperty('extendObjType')) {
746
+ parentObject = await getObjectSchema.getObjSchemaS3WithHierarchy(
747
+ _izContext,
748
+ objectSchema.extendObjType
749
+ );
750
+ }
751
+
752
+ return { objType, objectSchema, objectSchemas, parentObject };
753
+ }
754
+
755
+ /**
756
+ * Extracts field values matching storage resource specifications
757
+ * @param {Object} _izContext - Izara context object
758
+ * @param {string} storageTag - Storage resource tag
759
+ * @param {Object} createDataDetails - Complete data details map
760
+ * @param {Object} createDataDetail - Current storage data detail
761
+ * @param {Object} requestParams - Request parameters
762
+ * @param {Object} objInstanceFull - Full object instance with identifiers
763
+ * @returns {Object} Extracted fields object
764
+ */
765
+ export function extractFieldsByStorageTag(
766
+ _izContext,
767
+ storageTag,
768
+ createDataDetails,
769
+ createDataDetail,
770
+ requestParams,
771
+ objInstanceFull
772
+ ) {
773
+ const fields = {};
774
+ for (const fieldName of Object.keys(requestParams.fieldNames || {})) {
775
+ if (
776
+ createDataDetails[storageTag].fieldNames.includes(fieldName) &&
777
+ createDataDetail.fieldNames.includes(fieldName) &&
778
+ !objInstanceFull.identifiers.hasOwnProperty(fieldName) &&
779
+ !fields.hasOwnProperty(fieldName)
780
+ ) {
781
+ Object.assign(fields, {
782
+ [fieldName]: requestParams.fieldNames[fieldName]
783
+ });
784
+ }
785
+ }
786
+ return fields;
787
+ }
788
+
789
+ /**
790
+ * Auto-provisions owner TargetRole for DynamoDB-only objects (no Graph)
791
+ * @param {Object} _izContext - Izara context object
792
+ * @param {Object} options - Schema and identity parameters
793
+ * @returns {Promise<void>}
794
+ */
795
+ export async function autoProvisionOwnerTargetRoleForDynamoOnly(
796
+ _izContext,
797
+ { objectSchema, identifiersForCreateData, requestParams }
798
+ ) {
799
+ const hasRbacAddOn =
800
+ Array.isArray(objectSchema?.addOnDataStructure) &&
801
+ objectSchema.addOnDataStructure.some(addOn => addOn?.type === 'rbac');
802
+
803
+ const isRootRbacObject =
804
+ hasRbacAddOn &&
805
+ !objectSchema?.belongTo &&
806
+ objectSchema?.objectType !== 'user' &&
807
+ objectSchema?.generatedBy !== 'systemNoUser';
808
+
809
+ if (!isRootRbacObject) {
810
+ return;
811
+ }
812
+
813
+ try {
814
+ const submittedByUserId =
815
+ requestParams.submittedByUserId ||
816
+ _izContext.correlationIds?.get(coreConsts.SUBMITTED_BY_USER_ID);
817
+
818
+ if (submittedByUserId) {
819
+ const targetIdentKeys = Object.entries(identifiersForCreateData)
820
+ .map(([k, v]) => `${k}#${v}`)
821
+ .join('|');
822
+ const targetIdentifiersConcat =
823
+ targetIdentKeys ||
824
+ (identifiersForCreateData[Object.keys(identifiersForCreateData)[0]]
825
+ ? `${Object.keys(identifiersForCreateData)[0]}#${identifiersForCreateData[Object.keys(identifiersForCreateData)[0]]}`
826
+ : '');
827
+
828
+ if (targetIdentifiersConcat) {
829
+ const roleIdKey = `${targetIdentifiersConcat}_owner`;
830
+ _izContext.logger.debug(
831
+ '[<%- flowTag %>LogicalLib] Auto-provisioning owner TargetRole for DynamoDB-only:',
832
+ {
833
+ identifiersConcat: targetIdentifiersConcat,
834
+ roleIdKey,
835
+ submittedByUserId,
836
+ roleName: 'owner'
837
+ }
838
+ );
839
+
840
+ await dynamodbSharedLib.putItem(
841
+ _izContext,
842
+ dynamodbSharedLib.tableName(_izContext, 'TargetRoles'),
843
+ {
844
+ identifiersConcat: targetIdentifiersConcat,
845
+ roleIdKey,
846
+ submittedByUserId,
847
+ roleName: 'owner',
848
+ assignedAt: new Date().toISOString()
849
+ }
850
+ );
851
+
852
+ await dynamodbSharedLib.transactWriteItems(_izContext, [
853
+ {
854
+ action: 'put',
855
+ params: {
856
+ tableName: dynamodbSharedLib.tableName(
857
+ _izContext,
858
+ 'RoleUsers'
859
+ ),
860
+ attributes: {
861
+ roleIdKey,
862
+ userId: submittedByUserId,
863
+ submittedByUserId
864
+ }
865
+ }
866
+ },
867
+ {
868
+ action: 'put',
869
+ params: {
870
+ tableName: dynamodbSharedLib.tableName(
871
+ _izContext,
872
+ 'UserRoles'
873
+ ),
874
+ attributes: {
875
+ userId: submittedByUserId,
876
+ roleIdKey,
877
+ submittedByUserId
878
+ }
879
+ }
880
+ },
881
+ {
882
+ action: 'put',
883
+ params: {
884
+ tableName: dynamodbSharedLib.tableName(
885
+ _izContext,
886
+ 'RolePermissions'
887
+ ),
888
+ attributes: {
889
+ roleIdKey,
890
+ flowTypeConcat: `${process.env.iz_serviceTag}_Update`,
891
+ permission: true,
892
+ allow: 'accept',
893
+ parameterGroups: {},
894
+ submittedByUserId
895
+ }
896
+ }
897
+ },
898
+ {
899
+ action: 'put',
900
+ params: {
901
+ tableName: dynamodbSharedLib.tableName(
902
+ _izContext,
903
+ 'RolePermissions'
904
+ ),
905
+ attributes: {
906
+ roleIdKey,
907
+ flowTypeConcat: `${process.env.iz_serviceTag}_Delete`,
908
+ permission: true,
909
+ allow: 'accept',
910
+ parameterGroups: {},
911
+ submittedByUserId
912
+ }
913
+ }
914
+ }
915
+ ]);
916
+ }
917
+ }
918
+ } catch (roleError) {
919
+ _izContext.logger.error(
920
+ '[<%- flowTag %>LogicalLib] Failed to auto-provision owner TargetRole for DynamoDB-only:',
921
+ { roleError }
922
+ );
923
+ }
924
+ }
925
+
926
+ /**
927
+ * Resolves callback properties to attach to Graph dispatch message
928
+ * @param {Object} phaseResults - Results from previous phases
929
+ * @param {Object} requestParams - Request parameters
930
+ * @returns {Object} Formatted callback properties
931
+ */
932
+ export function resolveCallbackRbacProperties(phaseResults, requestParams) {
933
+ const uniqueIdentify =
934
+ phaseResults?.uniqueIdentify ||
935
+ Object.values(phaseResults || {}).find(
936
+ (r) => r?.returnValue?.uniqueIdentify
937
+ )?.returnValue?.uniqueIdentify;
938
+
939
+ const rbacType =
940
+ phaseResults?.rbacType ||
941
+ phaseResults?.target?.rbacType ||
942
+ phaseResults?.target?.returnValue?.rbacType ||
943
+ phaseResults?.app?.rbacType ||
944
+ phaseResults?.app?.returnValue?.rbacType ||
945
+ Object.values(phaseResults || {}).find(
946
+ (r) => r?.rbacType || r?.returnValue?.rbacType
947
+ )?.rbacType;
948
+
949
+ return {
950
+ uniqueIdentify,
951
+ targetIdentifiers: requestParams?.targetIdentifiers,
952
+ rbacType,
953
+ phaseResults
954
+ };
955
+ }
956
+
957
+ /**
958
+ * Dispatches node creation request to Graph Service
959
+ * @param {Object} _izContext - Izara context object
960
+ * @param {Object} options - Graph dispatch configuration
961
+ * @returns {Promise<void>}
962
+ */
963
+ export async function dispatchToGraphEngine(
964
+ _izContext,
965
+ {
966
+ objType,
967
+ objectForCreate,
968
+ graphServiceName,
969
+ requestParams,
970
+ parentFlowId,
971
+ callingFlowConfig,
972
+ phaseResults
973
+ }
974
+ ) {
975
+ let messageObject = {
976
+ objType,
977
+ objInstanceFull: objectForCreate.objInstanceFull,
978
+ deepPathConditional: requestParams.deepPathConditional || [],
979
+ setFieldConditionals: requestParams.setFieldConditionals || [],
980
+ relationships: requestParams.relationships || [],
981
+ targetIdentifiers: requestParams.targetIdentifiers,
982
+ parentFlowId: parentFlowId,
983
+ settings: { updatePropertiesOnMatch: false },
984
+ originTimestamp: Date.now()
985
+ };
986
+
987
+ const callbackProps = resolveCallbackRbacProperties(
988
+ phaseResults,
989
+ requestParams
990
+ );
991
+
992
+ messageObject =
993
+ callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
994
+ messageObject,
995
+ callingFlowSharedLib.addParentCallingFlowConfig(
996
+ callingFlowConfig,
997
+ callingFlowSharedLib.createCallingFlowConfig(
998
+ //(<callingFlowProperties>)
999
+ await lambdaSharedLib.lambdaFunctionName(
1000
+ _izContext,
1001
+ <% if (plan.hasCreateNodeCallback) { -%>
1002
+ 'CreateNodeCallbackHdrSqs'
1003
+ <% } else { -%>
1004
+ TOPIC_NAME_GENERATE_CODE.createNodeComplete
1005
+ <% } -%>
1006
+ ),
1007
+ <% if (plan.hasCreateNodeCallback) { -%>
1008
+ callbackProps
1009
+ <% } else { -%>
1010
+ {}
1011
+ <% } -%>
1012
+ //(</callingFlowProperties>)
1013
+ )
1014
+ )
1015
+ );
1016
+
1017
+ _izContext.logger.debug(
1018
+ `[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] message to graph:`,
1019
+ {
1020
+ graphServiceName,
1021
+ messageObject
1022
+ }
1023
+ );
1024
+
1025
+ await sns.publishAsync(_izContext, {
1026
+ Message: JSON.stringify(messageObject),
1027
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
1028
+ _izContext,
1029
+ TOPIC_NAME_GRAPH_HANDLER.inCreateNode,
1030
+ graphServiceName
1031
+ )
1032
+ });
1033
+ }
1034
+
1035
+ /**
1036
+ * Handles creation errors and publishes error response
1037
+ * @param {Object} _izContext - Izara context object
1038
+ * @param {Object} options - Error context details
1039
+ * @returns {Promise<Object>} Error status result
1040
+ */
1041
+ export async function handleCreateError(
1042
+ _izContext,
1043
+ {
1044
+ objType,
1045
+ objInstanceFull,
1046
+ requestParams,
1047
+ callingFlowConfig,
1048
+ errorsFound
1049
+ }
1050
+ ) {
1051
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] errorsFound:`, errorsFound);
1052
+
1053
+ await asyncFlowSharedLib.clearAllAwaitingSteps(
1054
+ _izContext,
1055
+ asyncFlowSharedLib.createPendingStepId(
1056
+ _izContext.uniqueRequestId,
1057
+ PREFIX.CREATE_OBJECT_ASYNC_COMPLETE
1058
+ )
1059
+ );
1060
+
1061
+ const messageObject =
1062
+ callingFlowSharedLib.addPassBackPropertiesToSnsResponseMessageObject(
1063
+ callingFlowConfig,
1064
+ { objType, identifiers: null, errorsFound }
1065
+ );
1066
+
1067
+ await sns.publishAsync(_izContext, {
1068
+ Message: JSON.stringify(messageObject),
1069
+ MessageAttributes: sns.createStringMessageAttributes(
1070
+ _izContext,
1071
+ callingFlowSharedLib.addCallingFlowToSnsResponseMessageAttributes(
1072
+ callingFlowConfig,
1073
+ {}
1074
+ )
1075
+ ),
1076
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
1077
+ _izContext,
1078
+ TOPIC_NAME_GENERATE_CODE.outCreateNodeComplete
1079
+ )
1080
+ });
1081
+
1082
+ return {
1083
+ objType,
1084
+ objInstanceFull,
1085
+ relationships: requestParams.relationships || [],
1086
+ targetIdentifiers: requestParams.targetIdentifiers,
1087
+ status: 'error',
1088
+ errorsFound
1089
+ };
1090
+ }
1091
+
1092
+ /**
1093
+ * Handles DynamoDB-only complete notification when no Graph callback is required
1094
+ * @param {Object} _izContext - Izara context object
1095
+ * @param {Object} options - Completion parameters
1096
+ * @returns {Promise<void>}
1097
+ */
1098
+ export async function handleDynamoDbOnlyComplete(
1099
+ _izContext,
1100
+ {
1101
+ objType,
1102
+ objInstanceFullForDynamoDb,
1103
+ callingFlowConfig
1104
+ }
1105
+ ) {
1106
+ const outMessage =
1107
+ callingFlowSharedLib.addPassBackPropertiesToSnsResponseMessageObject(
1108
+ callingFlowConfig,
1109
+ {
1110
+ objType: objType,
1111
+ objInstanceFull: {
1112
+ identifiers: objInstanceFullForDynamoDb.identifiers,
1113
+ fields: objInstanceFullForDynamoDb.fields
1114
+ }
1115
+ }
1116
+ );
1117
+
1118
+ if (
1119
+ callingFlowConfig[callingFlowSharedLib.consts.CALLINGFLOW_PROPERTYNAME]
1120
+ ) {
1121
+ _izContext.logger.debug('HAVE CallingFlow');
1122
+ await sns.publishAsync(_izContext, {
1123
+ Message: JSON.stringify(outMessage),
1124
+ MessageAttributes: sns.createStringMessageAttributes(
1125
+ _izContext,
1126
+ callingFlowSharedLib.addCallingFlowToSnsResponseMessageAttributes(
1127
+ callingFlowConfig,
1128
+ {}
1129
+ )
1130
+ ),
1131
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
1132
+ _izContext,
1133
+ TOPIC_NAME_GENERATE_CODE.outCreateNodeComplete
1134
+ )
1135
+ });
1136
+ }
1137
+ }
1138
+
1139
+ // ── 6. Main Logical Function ──────────────────────────────────────────────────
1140
+
64
1141
  /**
65
- * Core logical processing library
1142
+ * Core logical processing library for <%- flowTag %> flow
66
1143
  * @param {Object} _izContext - Context object with logger
67
- * @param {Object} requestParams - { fieldNames, relationships[] }
1144
+ * @param {Object} requestParams - Request parameters { fieldNames, relationships[] }
68
1145
  * @param {Object} callingFlowConfig - Calling flow configuration
1146
+ * @param {Array<string>} errorsFound - Array of preceding errors
69
1147
  * @param {Object} phaseResults - Results from preceding phases
70
1148
  * @returns {Promise<Object>} Result with objType, objInstanceFull, relationships, status, errorsFound
71
1149
  */
@@ -77,7 +1155,7 @@ export async function <%- flowTag %>LogicalLib(
77
1155
  phaseResults = {}
78
1156
  ) {
79
1157
  try {
80
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] started`, {
1158
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] started`, {
81
1159
  requestParams,
82
1160
  callingFlowConfig,
83
1161
  phaseResults,
@@ -87,47 +1165,23 @@ export async function <%- flowTag %>LogicalLib(
87
1165
  //(<beforeCreateMainLogical>)
88
1166
  //(</beforeCreateMainLogical>)
89
1167
 
90
- const objType = createObjType(requestParams.objectType);
1168
+ // 1. Load and resolve Schema
1169
+ const { objType, objectSchema, objectSchemas, parentObject } =
1170
+ await resolveObjectSchemas(_izContext, requestParams.objectType);
91
1171
 
92
- // ── Load schema ───────────────────────────────────────────────────────────
93
- const objectSchema = await getObjectSchema.getObjSchemaS3WithHierarchy(
94
- _izContext,
95
- objType
96
- );
97
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] objectSchema:`, objectSchema);
98
-
99
- const objectSchemas =
100
- uploadUseCase.generateObjectSchemaForCombineFieldNames(
101
- _izContext,
102
- objectSchema
103
- );
104
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] objectSchemas:`, objectSchemas);
105
-
106
- // ── Parent object ─────────────────────────────────────────────────────────
107
- let parentObject;
108
- if (objectSchemas.hasOwnProperty('extendObjType')) {
109
- parentObject = await getObjectSchema.getObjSchemaS3WithHierarchy(
110
- _izContext,
111
- objectSchema.extendObjType
112
- );
113
- }
114
-
115
- // ── Detect invocation type ────────────────────────────────────────────────
116
1172
  const directInvoke =
117
1173
  requestParams.hasOwnProperty('__context__') &&
118
1174
  requestParams.izEventSource?.eventSourceTag === 'DirectInvoke';
119
1175
 
120
- // ── Build createDataDetails ───────────────────────────────────────────────
121
1176
  const createDataDetails = await createDataDetailsLib(
122
1177
  _izContext,
123
1178
  objectSchemas
124
1179
  );
125
1180
  _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] createDataDetails:`, createDataDetails);
126
1181
 
127
- // ── Build objInstanceFull identifiers ─────────────────────────────────────
1182
+ // 2. Prepare objInstanceFull
128
1183
  const objInstanceFull = { identifiers: {}, fields: {} };
129
-
130
- for (const fieldName of Object.keys(requestParams.fieldNames)) {
1184
+ for (const fieldName of Object.keys(requestParams.fieldNames || {})) {
131
1185
  if (objectSchemas.identifierFieldNames.includes(fieldName)) {
132
1186
  Object.assign(objInstanceFull.identifiers, {
133
1187
  [fieldName]: requestParams.fieldNames[fieldName]
@@ -135,29 +1189,6 @@ export async function <%- flowTag %>LogicalLib(
135
1189
  }
136
1190
  }
137
1191
 
138
- // ── Helper: extract fields per storageTag ─────────────────────────────────
139
- function createObjInstanceFullFieldsByStorageTag(
140
- _izContext,
141
- storageTag,
142
- createDataDetail
143
- ) {
144
- const fields = {};
145
- for (const fieldName of Object.keys(requestParams.fieldNames)) {
146
- if (
147
- createDataDetails[storageTag].fieldNames.includes(fieldName) &&
148
- createDataDetail.fieldNames.includes(fieldName) &&
149
- !objInstanceFull.identifiers.hasOwnProperty(fieldName) &&
150
- !fields.hasOwnProperty(fieldName)
151
- ) {
152
- Object.assign(fields, {
153
- [fieldName]: requestParams.fieldNames[fieldName]
154
- });
155
- }
156
- }
157
- return fields;
158
- }
159
-
160
- // ── Process storage resources ─────────────────────────────────────────────
161
1192
  const objInstanceFullForDynamoDb = structuredClone(objInstanceFull);
162
1193
  const objInstanceFullForGraph = structuredClone(objInstanceFull);
163
1194
  const objInstanceFullForExternalTopic = structuredClone(objInstanceFull);
@@ -166,17 +1197,20 @@ export async function <%- flowTag %>LogicalLib(
166
1197
  const listOfObjectForCreates = [];
167
1198
  const listOfRecords = [];
168
1199
 
1200
+ // 3. Process storage resources
169
1201
  if (errorsFound.length === 0) {
170
1202
  //(<validateBeforeCreate>)
171
1203
  //(</validateBeforeCreate>)
172
1204
 
173
- for (const [storageTag, createDataDetail] of Object.entries(
174
- createDataDetails
175
- )) {
1205
+ const hasGraphStorage = Object.values(createDataDetails).some(
1206
+ (detail) => detail.storageType === consts.STORAGE_TYPES.graph
1207
+ );
1208
+
1209
+ for (const [storageTag, createDataDetail] of Object.entries(createDataDetails)) {
176
1210
  //(<beforeCreate>)
177
1211
  //(</beforeCreate>)
178
1212
 
179
- // ── DynamoDB ──────────────────────────────────────────────────────────
1213
+ // ── DynamoDB ──
180
1214
  if (createDataDetail.storageType === consts.STORAGE_TYPES.dynamoDB) {
181
1215
  _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] DynamoDB:`, {
182
1216
  storageTag,
@@ -197,18 +1231,16 @@ export async function <%- flowTag %>LogicalLib(
197
1231
 
198
1232
  Object.assign(
199
1233
  objInstanceFullForDynamoDb.fields,
200
- createObjInstanceFullFieldsByStorageTag(
1234
+ extractFieldsByStorageTag(
201
1235
  _izContext,
202
1236
  storageTag,
203
- createDataDetail
1237
+ createDataDetails,
1238
+ createDataDetail,
1239
+ requestParams,
1240
+ objInstanceFull
204
1241
  )
205
1242
  );
206
1243
 
207
- _izContext.logger.debug(
208
- 'objInstanceFull before create item in dynamoDB',
209
- objInstanceFullForDynamoDb
210
- );
211
-
212
1244
  const identifiersForCreateData =
213
1245
  await dynamoDbIdentifiersByStorageResource(
214
1246
  _izContext,
@@ -216,20 +1248,11 @@ export async function <%- flowTag %>LogicalLib(
216
1248
  createDataDetails,
217
1249
  objInstanceFullForDynamoDb.identifiers
218
1250
  );
219
- _izContext.logger.debug(
220
- 'identifiersForCreateData',
221
- identifiersForCreateData
222
- );
223
1251
 
224
1252
  //(<beforeCreateRecordDynamo>)
225
1253
  //(</beforeCreateRecordDynamo>)
226
1254
 
227
- if (
228
- Object.values(createDataDetails).some(
229
- (createDataDetail) =>
230
- createDataDetail.storageType === consts.STORAGE_TYPES.graph
231
- )
232
- ) {
1255
+ if (hasGraphStorage) {
233
1256
  _izContext.logger.debug('must process graph first');
234
1257
  listOfRecords.push({
235
1258
  [storageTag]: {
@@ -258,76 +1281,22 @@ export async function <%- flowTag %>LogicalLib(
258
1281
 
259
1282
  objInstanceFullForDynamoDb.fields = {};
260
1283
 
261
- // ── Auto-provision owner TargetRole for Root Object with RBAC ─────
262
- const hasRbacAddOn = Array.isArray(objectSchema?.addOnDataStructure) &&
263
- objectSchema.addOnDataStructure.some((addOn) => addOn?.type === 'rbac');
264
- const isRootRbacObject =
265
- hasRbacAddOn &&
266
- !objectSchema?.belongTo &&
267
- objectSchema?.objectType !== 'user' &&
268
- objectSchema?.generatedBy !== 'systemNoUser';
269
-
270
- if (isRootRbacObject) {
271
- try {
272
- const submittedByUserId = requestParams.submittedByUserId || _izContext.correlationIds.get(coreConsts.SUBMITTED_BY_USER_ID);
273
- if (submittedByUserId) {
274
- const targetIdentKeys = Object.entries(identifiersForCreateData)
275
- .map(([k, v]) => `${k}#${v}`)
276
- .join('_');
277
- const targetIdentifiersConcat = targetIdentKeys || (identifiersForCreateData[Object.keys(identifiersForCreateData)[0]] ? `${Object.keys(identifiersForCreateData)[0]}#${identifiersForCreateData[Object.keys(identifiersForCreateData)[0]]}` : '');
278
-
279
- if (targetIdentifiersConcat) {
280
- const roleIdKey = `${targetIdentifiersConcat}_owner`;
281
- _izContext.logger.debug('[CreateLogicalLib] Auto-provisioning owner TargetRole:', {
282
- identifiersConcat: targetIdentifiersConcat,
283
- roleIdKey,
284
- submittedByUserId,
285
- roleName: 'owner'
286
- });
287
-
288
- await dynamodbSharedLib.putItem(
289
- _izContext,
290
- dynamodbSharedLib.tableName(_izContext, 'TargetRoles'),
291
- {
292
- identifiersConcat: targetIdentifiersConcat,
293
- roleIdKey,
294
- submittedByUserId,
295
- roleName: 'owner',
296
- assignedAt: new Date().toISOString()
297
- }
298
- ).catch(() => {});
299
-
300
- await dynamodbSharedLib.transactWriteItems(_izContext, [
301
- {
302
- Put: {
303
- TableName: dynamodbSharedLib.tableName(_izContext, 'RoleUsers'),
304
- Item: { roleIdKey, submittedByUserId }
305
- }
306
- },
307
- {
308
- Put: {
309
- TableName: dynamodbSharedLib.tableName(_izContext, 'UserRoles'),
310
- Item: { submittedByUserId, roleIdKey }
311
- }
312
- }
313
- ]).catch(() => {});
314
- }
315
- }
316
- } catch (roleError) {
317
- _izContext.logger.warn('[CreateLogicalLib] Failed to auto-provision owner TargetRole:', { roleError });
318
- }
319
- }
1284
+ // Auto-provision owner TargetRole for DynamoDB-only
1285
+ await autoProvisionOwnerTargetRoleForDynamoOnly(_izContext, {
1286
+ objectSchema,
1287
+ identifiersForCreateData,
1288
+ requestParams
1289
+ });
320
1290
  }
321
1291
 
322
1292
  //(<afterCreateRecordDynamo>)
323
1293
  //(</afterCreateRecordDynamo>)
324
1294
 
325
- // ── Graph ─────────────────────────────────────────────────────────────
326
- } else if (
327
- createDataDetail.storageType === consts.STORAGE_TYPES.graph
328
- ) {
1295
+ // ── Graph ──
1296
+ } else if (createDataDetail.storageType === consts.STORAGE_TYPES.graph) {
329
1297
  //(<beforeCreateNode>)
330
1298
  //(</beforeCreateNode>)
1299
+
331
1300
  _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] Graph:`, {
332
1301
  storageTag,
333
1302
  objInstanceFull
@@ -335,16 +1304,15 @@ export async function <%- flowTag %>LogicalLib(
335
1304
 
336
1305
  Object.assign(
337
1306
  objInstanceFullForGraph.fields,
338
- createObjInstanceFullFieldsByStorageTag(
1307
+ extractFieldsByStorageTag(
339
1308
  _izContext,
340
1309
  storageTag,
341
- createDataDetail
1310
+ createDataDetails,
1311
+ createDataDetail,
1312
+ requestParams,
1313
+ objInstanceFull
342
1314
  )
343
1315
  );
344
- _izContext.logger.debug(
345
- 'objInstanceFull before send to Graph',
346
- objInstanceFullForGraph
347
- );
348
1316
 
349
1317
  //(<additionalAttributeCreateNode>)
350
1318
  const additionalAttributes = {};
@@ -372,29 +1340,22 @@ export async function <%- flowTag %>LogicalLib(
372
1340
  }
373
1341
  });
374
1342
 
375
- // ── External Topic ────────────────────────────────────────────────────
376
- } else if (
377
- createDataDetail.storageType === consts.STORAGE_TYPES.externalTopic
378
- ) {
379
- _izContext.logger.debug('::::::externalTopic::::::', {
380
- storageTag,
381
- createDataDetail
382
- });
1343
+ // ── External Topic ──
1344
+ } else if (createDataDetail.storageType === consts.STORAGE_TYPES.externalTopic) {
383
1345
  //(<beforeSendMessageToExternalTopic>)
384
1346
  //(</beforeSendMessageToExternalTopic>)
385
1347
 
386
1348
  Object.assign(
387
1349
  objInstanceFullForExternalTopic.fields,
388
- createObjInstanceFullFieldsByStorageTag(
1350
+ extractFieldsByStorageTag(
389
1351
  _izContext,
390
1352
  storageTag,
391
- createDataDetail
1353
+ createDataDetails,
1354
+ createDataDetail,
1355
+ requestParams,
1356
+ objInstanceFull
392
1357
  )
393
1358
  );
394
- _izContext.logger.debug(
395
- 'objInstanceFull before send to external topic',
396
- objInstanceFullForExternalTopic
397
- );
398
1359
 
399
1360
  allAwaitingStepsId.push({
400
1361
  awaitingStepId: await asyncFlowSharedLib.createAwaitingStepId(
@@ -416,9 +1377,8 @@ export async function <%- flowTag %>LogicalLib(
416
1377
  }
417
1378
  }
418
1379
 
419
- // ── Create awaiting multiple steps ────────────────────────────────────────
1380
+ // 4. Create awaiting multiple steps
420
1381
  if (allAwaitingStepsId.length > 0) {
421
- _izContext.logger.debug('not api will create awaitingStep');
422
1382
  await asyncFlowSharedLib.createAwaitingMultipleSteps(
423
1383
  _izContext,
424
1384
  asyncFlowSharedLib.createPendingStepId(
@@ -434,155 +1394,57 @@ export async function <%- flowTag %>LogicalLib(
434
1394
  );
435
1395
  }
436
1396
 
437
- // ── Process graph objects ─────────────────────────────────────────────────
438
- let messageObject = {};
1397
+ // 5. Dispatch to Graph Service
439
1398
  for (const objectCreate of listOfObjectForCreates) {
440
- for (const [graphServiceName, objectForCreate] of Object.entries(
441
- objectCreate
442
- )) {
443
- _izContext.logger.debug('Loop each graph:', {
444
- [graphServiceName]: objectForCreate
445
- });
446
-
447
- if (!objectForCreate.allStorageTagComplete) {
1399
+ for (const [graphServiceName, objectForCreate] of Object.entries(objectCreate)) {
1400
+ if (!objectForCreate.allStorageTagComplete && errorsFound.length === 0) {
448
1401
  //(<beforeSendMessageToGraph>)
449
1402
  //(</beforeSendMessageToGraph>)
450
- messageObject = {
1403
+
1404
+ await dispatchToGraphEngine(_izContext, {
1405
+ objType,
1406
+ objectForCreate,
1407
+ graphServiceName,
1408
+ requestParams,
1409
+ parentFlowId,
1410
+ callingFlowConfig,
1411
+ phaseResults
1412
+ });
1413
+
1414
+ //(<afterSendMessageToGraph>)
1415
+ //(</afterSendMessageToGraph>)
1416
+
1417
+ return {
451
1418
  objType,
452
- objInstanceFull: objectForCreate.objInstanceFull,
453
- deepPathConditional: requestParams.deepPathConditional || [],
454
- setFieldConditionals: requestParams.setFieldConditionals || [],
1419
+ objInstanceFull: objInstanceFullForGraph,
455
1420
  relationships: requestParams.relationships || [],
456
- parentFlowId: parentFlowId
1421
+ targetIdentifiers: requestParams.targetIdentifiers,
1422
+ status: 'complete',
1423
+ errorsFound
457
1424
  };
458
1425
  }
459
-
460
- if (errorsFound.length > 0) break;
461
-
462
- Object.assign(messageObject, {
463
- settings: { updatePropertiesOnMatch: false },
464
- originTimestamp: Date.now()
465
- });
466
-
467
- messageObject =
468
- callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
469
- messageObject,
470
- callingFlowSharedLib.addParentCallingFlowConfig(
471
- callingFlowConfig,
472
- callingFlowSharedLib.createCallingFlowConfig(
473
- //(<callingFlowProperties>)
474
- await lambdaSharedLib.lambdaFunctionName(
475
- _izContext,
476
- TOPIC_NAME_GENERATE_CODE.createNodeComplete
477
- ),
478
- {}
479
- //(</callingFlowProperties>)
480
- )
481
- )
482
- );
483
-
484
- await sns.publishAsync(_izContext, {
485
- Message: JSON.stringify(messageObject),
486
- TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
487
- _izContext,
488
- TOPIC_NAME_GRAPH_HANDLER.inCreateNode,
489
- graphServiceName
490
- )
491
- });
492
-
493
- //(<afterSendMessageToGraph>)
494
- //(</afterSendMessageToGraph>)
495
- return {
496
- objType,
497
- objInstanceFull: objInstanceFullForGraph,
498
- relationships: requestParams.relationships || [],
499
- targetIdentifiers: requestParams.targetIdentifiers,
500
- status: 'complete',
501
- errorsFound
502
- };
503
1426
  }
504
1427
  if (errorsFound.length > 0) break;
505
1428
  }
506
1429
 
507
- // ── Handle errors ─────────────────────────────────────────────────────────
1430
+ // 6. Handle Errors
508
1431
  if (errorsFound.length > 0) {
509
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>LogicalLib] errorsFound:`, errorsFound);
510
-
511
- await asyncFlowSharedLib.clearAllAwaitingSteps(
512
- _izContext,
513
- asyncFlowSharedLib.createPendingStepId(
514
- _izContext.uniqueRequestId,
515
- PREFIX.CREATE_OBJECT_ASYNC_COMPLETE
516
- )
517
- );
518
-
519
- messageObject =
520
- callingFlowSharedLib.addPassBackPropertiesToSnsResponseMessageObject(
521
- callingFlowConfig,
522
- { objType, identifiers: null, errorsFound }
523
- );
524
-
525
- await sns.publishAsync(_izContext, {
526
- Message: JSON.stringify(messageObject),
527
- MessageAttributes: sns.createStringMessageAttributes(
528
- _izContext,
529
- callingFlowSharedLib.addCallingFlowToSnsResponseMessageAttributes(
530
- callingFlowConfig,
531
- {}
532
- )
533
- ),
534
- TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
535
- _izContext,
536
- TOPIC_NAME_GENERATE_CODE.outCreateNodeComplete
537
- )
538
- });
539
-
540
- return {
1432
+ return await handleCreateError(_izContext, {
541
1433
  objType,
542
1434
  objInstanceFull,
543
- relationships: requestParams.relationships || [],
544
- targetIdentifiers: requestParams.targetIdentifiers,
545
- status: 'error',
1435
+ requestParams,
1436
+ callingFlowConfig,
546
1437
  errorsFound
547
- };
1438
+ });
548
1439
  }
549
1440
 
550
- // ── DynamoDB-only complete path ───────────────────────────────────────────
551
- if (
552
- allAwaitingStepsId.length === 0 &&
553
- listOfObjectForCreates.length === 0
554
- ) {
555
- const outMessage =
556
- callingFlowSharedLib.addPassBackPropertiesToSnsResponseMessageObject(
557
- callingFlowConfig,
558
- {
559
- objType: objType,
560
- objInstanceFull: {
561
- identifiers: objInstanceFullForDynamoDb.identifiers,
562
- fields: objInstanceFullForDynamoDb.fields
563
- }
564
- }
565
- );
566
-
567
- if (
568
- callingFlowConfig[callingFlowSharedLib.consts.CALLINGFLOW_PROPERTYNAME]
569
- ) {
570
- _izContext.logger.debug('HAVE CallingFlow');
571
- await sns.publishAsync(_izContext, {
572
- Message: JSON.stringify(outMessage),
573
- MessageAttributes: sns.createStringMessageAttributes(
574
- _izContext,
575
- callingFlowSharedLib.addCallingFlowToSnsResponseMessageAttributes(
576
- callingFlowConfig,
577
- {}
578
- )
579
- ),
580
- TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
581
- _izContext,
582
- TOPIC_NAME_GENERATE_CODE.outCreateNodeComplete
583
- )
584
- });
585
- }
1441
+ // 7. Handle DynamoDB-only Complete Path
1442
+ if (allAwaitingStepsId.length === 0 && listOfObjectForCreates.length === 0) {
1443
+ await handleDynamoDbOnlyComplete(_izContext, {
1444
+ objType,
1445
+ objInstanceFullForDynamoDb,
1446
+ callingFlowConfig
1447
+ });
586
1448
  }
587
1449
 
588
1450
  return {