@izara_project/izara-core-generate-service-code 1.0.90 → 1.0.92

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 (82) hide show
  1. package/package.json +7 -10
  2. package/src/compile/flowValidator.js +6 -0
  3. package/src/compile/loadSchemas.js +177 -69
  4. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +2 -2
  5. package/src/emit/crud/generateCrudApplicationArtifacts.js +15 -17
  6. package/src/emit/crud/generateDynamicCrudFlowSchemas.js +16 -21
  7. package/src/emit/crud/loadLocalSchemaArrays.js +4 -8
  8. package/src/emit/crud/templates/CreateAfterHookCallback_Main.ejs +6 -6
  9. package/src/emit/crud/templates/CreateBeforeHookCallback_Main.ejs +3 -3
  10. package/src/emit/crud/templates/CreateComplete_Main.ejs +22 -22
  11. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +18 -18
  12. package/src/emit/crud/templates/CreateLogicalLib.ejs +47 -47
  13. package/src/emit/crud/templates/CreateNodeCallback_Main.ejs +24 -24
  14. package/src/emit/crud/templates/DeleteAfterHookCallback_Main.ejs +5 -5
  15. package/src/emit/crud/templates/DeleteBeforeHookCallback_Main.ejs +3 -3
  16. package/src/emit/crud/templates/DeleteComplete_Main.ejs +23 -23
  17. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +15 -15
  18. package/src/emit/crud/templates/DeleteLogicalLib.ejs +19 -19
  19. package/src/emit/crud/templates/FlowEndpointRbacCallback_Main.ejs +3 -3
  20. package/src/emit/crud/templates/UpdateAfterHookCallback_Main.ejs +5 -5
  21. package/src/emit/crud/templates/UpdateBeforeHookCallback_Main.ejs +3 -3
  22. package/src/emit/crud/templates/UpdateComplete_Main.ejs +22 -22
  23. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +15 -15
  24. package/src/emit/crud/templates/UpdateLogicalLib.ejs +33 -33
  25. package/src/emit/custom/customFlowPaths.js +149 -0
  26. package/src/emit/custom/customFlowS3.js +156 -0
  27. package/src/emit/custom/customFlowSteps.js +385 -0
  28. package/src/emit/custom/customFlowTemplates.js +28 -0
  29. package/src/emit/custom/customFlows.js +48 -19
  30. package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +57 -692
  31. package/src/emit/custom/generateCustomFlowApplicationArtifacts.js +27 -25
  32. package/src/emit/custom/templates/FlowEndpointRbacCallback_Main.ejs +7 -7
  33. package/src/emit/custom/templates/s3/ConfirmReserved_HdrSqs.ejs +3 -3
  34. package/src/emit/custom/templates/s3/ConfirmReserved_Main.ejs +3 -3
  35. package/src/emit/custom/templates/s3/CreatePreSignUrl_HdrSqs.ejs +3 -3
  36. package/src/emit/custom/templates/s3/CreatePreSignUrl_Main.ejs +4 -4
  37. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +26 -6
  38. package/src/emit/rbac/emitRbacApplicationArtifacts.js +40 -13
  39. package/src/emit/rbac/generateCreateTargetRoleHandler.js +11 -13
  40. package/src/emit/rbac/generateDynamicRbacFlowSchemas.js +14 -18
  41. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +13 -7
  42. package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +1 -1
  43. package/src/emit/relationship/generateDynamicRelationshipFlowSchemas.js +16 -19
  44. package/src/emit/relationship/generateRelationshipApplicationArtifacts.js +19 -19
  45. package/src/emit/relationship/templates/ChangeRelationshipComplete_HdrSqs.ejs +2 -2
  46. package/src/emit/relationship/templates/ChangeRelationshipComplete_Main.ejs +6 -6
  47. package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +17 -17
  48. package/src/emit/relationship/templates/CreateRelationshipComplete_HdrSqs.ejs +2 -2
  49. package/src/emit/relationship/templates/CreateRelationshipComplete_Main.ejs +6 -6
  50. package/src/emit/relationship/templates/CreateRelationship_Main.ejs +14 -14
  51. package/src/emit/relationship/templates/DeleteRelationshipComplete_HdrSqs.ejs +2 -2
  52. package/src/emit/relationship/templates/DeleteRelationshipComplete_Main.ejs +6 -6
  53. package/src/emit/relationship/templates/DeleteRelationship_Main.ejs +10 -10
  54. package/src/emit/relationship/templates/GetRelationshipComplete_HdrSqs.ejs +2 -2
  55. package/src/emit/relationship/templates/GetRelationshipComplete_Main.ejs +5 -5
  56. package/src/emit/relationship/templates/GetRelationship_Main.ejs +9 -9
  57. package/src/emit/relationship/templates/MoveRelationshipComplete_HdrSqs.ejs +2 -2
  58. package/src/emit/relationship/templates/MoveRelationshipComplete_Main.ejs +6 -6
  59. package/src/emit/relationship/templates/MoveRelationship_Main.ejs +15 -15
  60. package/src/emit/relationship/templates/UpdateRelationshipComplete_HdrSqs.ejs +2 -2
  61. package/src/emit/relationship/templates/UpdateRelationshipComplete_Main.ejs +6 -6
  62. package/src/emit/relationship/templates/UpdateRelationship_Main.ejs +11 -11
  63. package/src/emit/resource/dynamoResourceArtifacts.js +18 -0
  64. package/src/emit/resource/emitResourceArtifacts.js +13 -1251
  65. package/src/emit/resource/generateResourceArtifacts.js +20 -20
  66. package/src/emit/resource/iamResourceArtifacts.js +612 -0
  67. package/src/emit/resource/resourceState.js +564 -0
  68. package/src/emit/resource/resourceTemplate.js +30 -0
  69. package/src/emit/resource/s3ResourceArtifacts.js +14 -0
  70. package/src/emit/resource/sqsResourceArtifacts.js +113 -0
  71. package/src/emit/shared/runSubsystemGeneration.js +55 -0
  72. package/src/emit/shared/templates/FindData_Main.ejs +19 -19
  73. package/src/emit/shared/templates/PaginateProcessLogical_Main.ejs +3 -3
  74. package/src/emit/shared/templates/ProcessLogical_Main.ejs +15 -15
  75. package/src/emit/system/emitSystemWebSocketArtifacts.js +2 -1
  76. package/test/createTargetRoleHandler.test.js +62 -4
  77. package/test/crudApplicationArtifacts.test.js +75 -3
  78. package/test/customFlowApplicationArtifacts.test.js +98 -0
  79. package/test/dynamicCrudFlowSchemas.test.js +2 -2
  80. package/test/dynamicRbacFlowSchemas.test.js +32 -4
  81. package/test/relationshipApplicationArtifacts.test.js +1 -1
  82. package/test/resourceArtifacts.test.js +99 -0
@@ -1,95 +1,19 @@
1
- import ejsLib from 'ejs';
2
- import { readFile } from 'node:fs/promises';
3
-
1
+ import { normalizeYaml } from './resourceTemplate.js';
2
+ import { createResourceState } from './resourceState.js';
4
3
  import {
5
- customFlowRoleName,
6
- flowDsqSteps,
7
- flowExtTopicSteps,
8
- generatedDsqFlows,
9
- generatedOwnTopicFlows,
10
- supportedCustomFlows,
11
- flowHasRbac
12
- } from '../custom/customFlows.js';
13
- import { hasSystemWebSocket } from '../system/emitSystemWebSocketArtifacts.js';
14
- import { rbacFlowTag } from '../rbac/rbacFlowTag.js';
15
- import { CRUD_ACTIONS, createCrudActionPlans } from '../crud/crudActionPlan.js';
4
+ renderCrudInSqs,
5
+ renderRelationshipInSqs,
6
+ renderRbacInSqs,
7
+ renderCustomFlowInSqs,
8
+ renderSystemInSqs,
9
+ renderOut
10
+ } from './sqsResourceArtifacts.js';
11
+ import { renderDynamo } from './dynamoResourceArtifacts.js';
12
+ import { renderIam, renderLambdaRoleConfig } from './iamResourceArtifacts.js';
13
+ import { renderS3 } from './s3ResourceArtifacts.js';
16
14
 
17
- const TEMPLATE_URL = new URL('./templates/', import.meta.url);
18
15
  const RESOURCE_ROOT = 'resource/sls_yaml/generatedCode/source';
19
16
  const APP_ROOT = 'app/sls_yaml/generatedCode/source';
20
- const INITIAL_SETUP_ROOT = 'app/initial_setup/generatedCode/source';
21
- const RELATIONSHIP_ACTIONS = [
22
- 'CreateRelationship',
23
- 'UpdateRelationship',
24
- 'DeleteRelationship',
25
- 'GetRelationship',
26
- 'ChangeRelationship',
27
- 'MoveRelationship'
28
- ];
29
- const RBAC_ACTIONS = [
30
- 'CreateTargetRole',
31
- 'UpdateTargetRole',
32
- 'DeleteTargetRole',
33
- 'GetTargetRole',
34
- 'ListTargetRole',
35
- 'CreateRolePermissions',
36
- 'ListRolePermissions',
37
- 'DeleteRolePermissions',
38
- 'CreateUserRole',
39
- 'ListUserInRoles',
40
- 'DeleteUserFromRole',
41
- 'GetMyPermissions',
42
- 'ValidatorRbac',
43
- 'ReservedDynamicUsage',
44
- 'ConfirmUsage',
45
- 'CancelUsage'
46
- ];
47
- const SYSTEM_TABLES = [
48
- 'WebSocketTask',
49
- 'UserUploadRecords',
50
- 'RegisterRecords',
51
- 'AwaitingMultipleSteps',
52
- 'AwaitingMultipleStepByPending',
53
- 'FindDataMain',
54
- 'LogicalResultsMain',
55
- 'LogicalResultsData',
56
- 'AwaitingStep'
57
- ];
58
- const RBAC_TABLES = [
59
- 'Roles',
60
- 'TargetRoles',
61
- 'RolePermissions',
62
- 'RoleUsers',
63
- 'UserRoles',
64
- 'RbacPermissionCaches',
65
- 'RunningTotalUsage',
66
- 'RunningTotalReservedUsage',
67
- 'WebSocketTask'
68
- ];
69
- const LEGACY_PROCESS_QUEUES = [
70
- 'FindDataHdrSqs',
71
- 'ProcessLogicalHdrSqs',
72
- 'PaginateProcessLogicalHdrDsq',
73
- 'PaginateProcessLogicalHdrSqs'
74
- ];
75
- const DYNAMO_ACTIONS = [
76
- 'dynamodb:DeleteItem',
77
- 'dynamodb:GetItem',
78
- 'dynamodb:PutItem',
79
- 'dynamodb:Query',
80
- 'dynamodb:UpdateItem'
81
- ];
82
- const SQS_CONSUMER_ACTIONS = [
83
- 'sqs:DeleteMessage',
84
- 'sqs:GetQueueAttributes',
85
- 'sqs:ReceiveMessage'
86
- ];
87
- const SQS_DLQ_ACTIONS = [
88
- 'sqs:DeleteMessage',
89
- 'sqs:GetQueueAttributes',
90
- 'sqs:ReceiveMessage',
91
- 'sqs:SendMessage'
92
- ];
93
17
 
94
18
  export async function emitResourceArtifacts({
95
19
  objects,
@@ -201,1154 +125,7 @@ export const resourceArtifactPaths = [
201
125
  `${APP_ROOT}/cors.yml`
202
126
  ];
203
127
 
204
- function createResourceState({ objects, relationships, flows }) {
205
- const crud = objects.some(isCrudObject);
206
- const selfRbac = objects.some(hasSelfRbacAddOn);
207
- const rbac = objects.some((obj) => hasRbacAddOn(obj, objects));
208
- const crudActionPlans = createCrudActionPlans(objects, flows);
209
-
210
- const crudOwnTopics = crud ? [...CRUD_ACTIONS] : [];
211
- const crudSqsQueues = [];
212
- if (crud) {
213
- for (const action of ['Create', 'Update', 'Delete']) {
214
- const plan = crudActionPlans[action];
215
- if (plan.hasCreateNodeCallback) {
216
- crudSqsQueues.push(`${action}NodeCallbackHdrSqs`);
217
- }
218
- if (plan.hasBeforeHookCallback) {
219
- crudSqsQueues.push(`${action}BeforeHookCallbackHdrSqs`);
220
- }
221
- if (plan.hasAfterHookCallback) {
222
- crudSqsQueues.push(`${action}AfterHookCallbackHdrSqs`);
223
- }
224
- if (plan.hasComplete) {
225
- crudSqsQueues.push(`${action}CompleteHdrSqs`);
226
- }
227
- }
228
- }
229
-
230
- const relationshipOwnTopics =
231
- relationships.length > 0 ? [...RELATIONSHIP_ACTIONS] : [];
232
- const relationshipSqsQueues =
233
- relationships.length > 0
234
- ? RELATIONSHIP_ACTIONS.map((action) => `${action}CompleteHdrSqs`)
235
- : [];
236
- const relationshipDsqQueues =
237
- relationships.length > 0 ? ['GetRelationshipHdrDsq'] : [];
238
-
239
- const rbacOwnTopics = [];
240
- const rbacSqsQueues = [];
241
- if (selfRbac) {
242
- for (const action of RBAC_ACTIONS) {
243
- rbacOwnTopics.push(action);
244
- rbacSqsQueues.push(`Process${action}HdrSqs`);
245
- }
246
- }
247
- if (selfRbac) {
248
- const flowTags = [
249
- ...new Set(objects.filter(hasSelfRbacAddOn).map(rbacFlowTag))
250
- ];
251
- for (const flowTag of flowTags) {
252
- rbacOwnTopics.push(flowTag);
253
- }
254
- }
255
- if (crud) {
256
- for (const action of ['Create', 'Update', 'Delete']) {
257
- const plan = crudActionPlans[action];
258
- if (plan.hasRbacCallback) {
259
- rbacSqsQueues.push(`${action}RbacCallbackHdrSqs`);
260
- }
261
- }
262
- }
263
-
264
- const customOwnTopics = flows
265
- .filter((flow) => includesEvent(flow, 'ownTopic'))
266
- .map((flow) => upperFirst(flow.flowTag));
267
-
268
- const customSqsQueues = [
269
- ...flows
270
- .filter((flow) => includesEvent(flow, 'extTopic'))
271
- .map((flow) => `Process${upperFirst(flow.flowTag)}HdrSqs`),
272
- ...flows
273
- .filter(
274
- (flow) =>
275
- flow?.outputTopic &&
276
- !flow?.generatedCodeConfig &&
277
- upperFirst(flow.flowTag)
278
- )
279
- .map((flow) => `${upperFirst(flow.flowTag)}CompleteHdrSqs`)
280
- ];
281
- for (const { flowTag, stepName } of flowExtTopicSteps(flows)) {
282
- customSqsQueues.push(`${flowTag}${stepName}HdrSqs`);
283
- }
284
- for (const flow of flows) {
285
- if (flowHasRbac(flow, objects)) {
286
- const tag = upperFirst(flow.flowTag);
287
- customSqsQueues.push(`${tag}RbacCallbackHdrSqs`);
288
- }
289
- }
290
- for (const flow of flows.filter((flow) => includesEvent(flow, 's3'))) {
291
- const tag = upperFirst(flow.flowTag);
292
- customSqsQueues.push(`${tag}CreatePreSignUrlHdrSqs`);
293
- customSqsQueues.push(`${tag}ReservedLimitHdrSqs`);
294
- customSqsQueues.push(`${tag}ConfirmAfterUploadHdrSqs`);
295
- }
296
-
297
- const customDsqQueues = flows
298
- .filter((flow) => includesEvent(flow, 'dsq'))
299
- .map((flow) => `${upperFirst(flow.flowTag)}HdrDsq`);
300
- for (const { flowTag, stepName } of flowDsqSteps(flows)) {
301
- customDsqQueues.push(`${flowTag}${stepName}HdrDsq`);
302
- }
303
-
304
- const websocket = hasSystemWebSocket(flows, {
305
- includeRelationshipOwnTopic: true,
306
- objects,
307
- relationships
308
- });
309
-
310
- const systemOwnTopics = ['ProcessLogical', 'FindData'];
311
- const systemSqsQueues = [
312
- 'PaginateProcessLogicalHdrSqs',
313
- ...(websocket ? ['WebSocketCompleteHdrSqs', 'RegisterCompleteHdrSqs'] : [])
314
- ];
315
- const systemDsqQueues = ['PaginateProcessLogicalHdrDsq'];
316
-
317
- const ownTopics = new Set([
318
- ...systemOwnTopics,
319
- ...crudOwnTopics,
320
- ...relationshipOwnTopics,
321
- ...rbacOwnTopics,
322
- ...customOwnTopics
323
- ]);
324
- const sqsQueues = new Set([
325
- ...systemSqsQueues,
326
- ...crudSqsQueues,
327
- ...relationshipSqsQueues,
328
- ...rbacSqsQueues,
329
- ...customSqsQueues
330
- ]);
331
- const dsqQueues = new Set([
332
- ...systemDsqQueues,
333
- ...relationshipDsqQueues,
334
- ...customDsqQueues
335
- ]);
336
-
337
- const customRoles = [
338
- ...new Set([
339
- ...supportedCustomFlows(flows).map((flow) =>
340
- customFlowRoleName(flow.flowTag)
341
- ),
342
- ...generatedOwnTopicFlows(flows).map((flow) =>
343
- customFlowRoleName(flow.flowTag)
344
- ),
345
- ...generatedDsqFlows(flows).map((flow) =>
346
- customFlowRoleName(flow.flowTag)
347
- ),
348
- ...flowExtTopicSteps(flows).map(({ flow }) =>
349
- customFlowRoleName(flow.flowTag)
350
- ),
351
- ...flowDsqSteps(flows).map(({ flow }) => customFlowRoleName(flow.flowTag))
352
- ])
353
- ];
354
- const webSocketRoles = new Set();
355
- if (websocket) {
356
- webSocketRoles.add('WebSocketMainRole');
357
- webSocketRoles.add('RegisterRole');
358
- }
359
- if (crud) webSocketRoles.add('FlowObjectsRole');
360
- if (relationships.length > 0) webSocketRoles.add('FlowRelationshipRole');
361
- if (rbac) webSocketRoles.add('FlowRbacRole');
362
- const filteredWsFlows = [
363
- ...supportedCustomFlows(flows),
364
- ...generatedOwnTopicFlows(flows)
365
- ].filter(
366
- (flow) => includesEvent(flow, 'ownTopic') || includesEvent(flow, 's3')
367
- );
368
- for (const flow of filteredWsFlows) {
369
- webSocketRoles.add(customFlowRoleName(flow.flowTag));
370
- }
371
- const outTopics = new Set([
372
- ...ownTopics,
373
- ...flows
374
- .filter((flow) => flow?.outputTopic && upperFirst(flow.flowTag))
375
- .map((flow) => upperFirst(flow.flowTag))
376
- ]);
377
- const objectTableNames = [...collectTables(objects).keys()];
378
- const relationshipTableNames = [...collectTables(relationships).keys()];
379
- const tableMap = new Map([
380
- ...collectTables(objects),
381
- ...collectTables(relationships),
382
- ...collectTables(flows)
383
- ]);
384
- const externalDynamoTables = objects.flatMap(
385
- (object) => object.externalDynamoResources ?? []
386
- );
387
- const s3Buckets = flows
388
- .filter((flow) => includesEvent(flow, 's3') && flow.createBucket === true)
389
- .map((flow) => flow.bucketName)
390
- .filter(Boolean);
391
- const s3FlowTags = flows
392
- .filter((flow) => includesEvent(flow, 's3'))
393
- .map((flow) => upperFirst(flow.flowTag));
394
- const s3Subscriptions = flows
395
- .filter((flow) => includesEvent(flow, 's3'))
396
- .map((flow) => {
397
- const tag = upperFirst(flow.flowTag);
398
- const outputTopicStr =
399
- typeof flow.outputTopic === 'string' ? flow.outputTopic : null;
400
- const topicName = outputTopicStr
401
- ? outputTopicStr.startsWith('Out')
402
- ? outputTopicStr
403
- : `Out${upperFirst(outputTopicStr)}`
404
- : flow.s3Subscription?.topicName || 'OutCreate';
405
- const queueName =
406
- flow.s3Subscription?.queueName || `${tag}ReservedLimitHdrSqs`;
407
- const subscriptionName =
408
- flow.s3Subscription?.subscriptionName ||
409
- `Subscription${topicName}To${queueName}`;
410
- return { subscriptionName, topicName, queueName };
411
- });
412
- const systemTables = [...SYSTEM_TABLES];
413
- if (rbac) {
414
- systemTables.push(
415
- 'Roles',
416
- 'TargetRoles',
417
- 'RolePermissions',
418
- 'RoleUsers',
419
- 'UserRoles',
420
- 'RbacPermissionCaches',
421
- 'RunningTotalUsage',
422
- 'RunningTotalReservedUsage'
423
- );
424
- }
425
- if (s3FlowTags.length > 0) {
426
- systemTables.push('ReservedDataMain');
427
- }
428
- const hasHttpApi =
429
- crud ||
430
- relationships.length > 0 ||
431
- rbac ||
432
- flows.some((flow) => includesEvent(flow, 'lambdaSyncApi'));
433
- return {
434
- crud,
435
- rbac,
436
- selfRbac,
437
- crudActionPlans,
438
- crudOwnTopics: [...new Set(crudOwnTopics)].filter(Boolean).sort(),
439
- crudSqsQueues: [...new Set(crudSqsQueues)].filter(Boolean).sort(),
440
- relationshipOwnTopics: [...new Set(relationshipOwnTopics)]
441
- .filter(Boolean)
442
- .sort(),
443
- relationshipSqsQueues: [...new Set(relationshipSqsQueues)]
444
- .filter(Boolean)
445
- .sort(),
446
- relationshipDsqQueues: [...new Set(relationshipDsqQueues)]
447
- .filter(Boolean)
448
- .sort(),
449
- rbacOwnTopics: [...new Set(rbacOwnTopics)].filter(Boolean).sort(),
450
- rbacSqsQueues: [...new Set(rbacSqsQueues)].filter(Boolean).sort(),
451
- customOwnTopics: [...new Set(customOwnTopics)].filter(Boolean).sort(),
452
- customSqsQueues: [...new Set(customSqsQueues)].filter(Boolean).sort(),
453
- customDsqQueues: [...new Set(customDsqQueues)].filter(Boolean).sort(),
454
- systemOwnTopics: [...new Set(systemOwnTopics)].filter(Boolean).sort(),
455
- systemSqsQueues: [...new Set(systemSqsQueues)].filter(Boolean).sort(),
456
- systemDsqQueues: [...new Set(systemDsqQueues)].filter(Boolean).sort(),
457
- s3Subscriptions,
458
- s3FlowTags,
459
- hasHttpApi,
460
- relationships: relationships.length > 0,
461
- ownTopics: [...ownTopics].filter(Boolean).sort(),
462
- sqsQueues: [...sqsQueues].filter(Boolean).sort(),
463
- dsqQueues: [...dsqQueues].filter(Boolean).sort(),
464
- customRoles,
465
- websocket,
466
- webSocketRoles,
467
- webSocketSubscriptions: websocket
468
- ? [...outTopics]
469
- .filter(
470
- (topic) =>
471
- flows.some(
472
- (flow) =>
473
- (flow?.outputTopic ||
474
- (includesEvent(flow, 'ownTopic') &&
475
- (flow.initiateBy === 'user' ||
476
- flow.initiateBy === 'systemWithUser'))) &&
477
- upperFirst(flow.flowTag) === topic
478
- ) ||
479
- (objects.length > 0 &&
480
- topic !== 'Get' &&
481
- CRUD_ACTIONS.includes(topic)) ||
482
- (relationships.length > 0 &&
483
- topic !== 'GetRelationship' &&
484
- RELATIONSHIP_ACTIONS.includes(topic))
485
- )
486
- .sort()
487
- : [],
488
- outTopics: [...outTopics].filter(Boolean).sort(),
489
- tables: [...tableMap.entries()].map(([tableName, keys]) => ({
490
- tableName,
491
- ...keys
492
- })),
493
- allTableNames: [...new Set([...systemTables, ...tableMap.keys()])].sort(),
494
- objectTableNames,
495
- relationshipTableNames,
496
- externalDynamoTables,
497
- objectTablesByType: new Map(
498
- objects
499
- .map((object) => [
500
- object.objectType,
501
- Object.values(object.storageResources ?? {}).find(
502
- (resource) =>
503
- resource?.storageType === 'dynamo' ||
504
- resource?.storageType === 'dynamoDB' ||
505
- !resource?.storageType
506
- )?.tableName
507
- ])
508
- .filter(([, tableName]) => tableName)
509
- ),
510
- s3Buckets,
511
- flows,
512
- objects
513
- };
514
- }
515
-
516
- function indentYaml(content, spaces = 2) {
517
- const prefix = ' '.repeat(spaces);
518
- return content
519
- .split('\n')
520
- .map((line) => {
521
- if (line.trim()) {
522
- return `${prefix}${line}`;
523
- }
524
- return '';
525
- })
526
- .join('\n');
527
- }
528
-
529
- async function renderS3(state) {
530
- if (state.s3Buckets.length === 0) return 'Resources:\n';
531
- const template = await readFile(
532
- new URL('S3Bucket_Yaml.ejs', TEMPLATE_URL),
533
- 'utf8'
534
- );
535
- const chunks = await Promise.all(
536
- state.s3Buckets.map((bucketName) => ejsLib.render(template, { bucketName }))
537
- );
538
- return `Resources:\n${indentYaml(chunks.join('\n'), 2)}`;
539
- }
540
-
541
- async function renderResourceGroup({
542
- ownTopics = [],
543
- sqsQueues = [],
544
- dsqQueues = [],
545
- webSocketSubscriptions = [],
546
- s3Subscriptions = []
547
- }) {
548
- const hasResources =
549
- ownTopics.length > 0 ||
550
- sqsQueues.length > 0 ||
551
- dsqQueues.length > 0 ||
552
- webSocketSubscriptions.length > 0 ||
553
- s3Subscriptions.length > 0;
554
- if (!hasResources) return '';
555
-
556
- const chunks = await Promise.all([
557
- ...ownTopics.map((queueName) => render('ResourceYaml.ejs', { queueName })),
558
- ...sqsQueues.map((queueName) =>
559
- render('ResourceSqsYaml.ejs', { queueName })
560
- ),
561
- ...dsqQueues.map((queueName) =>
562
- render('ResourceSqsYaml.ejs', { queueName })
563
- )
564
- ]);
565
- const wsSubs = await Promise.all(
566
- webSocketSubscriptions.map((topicName) =>
567
- renderWebSocketSubscription(topicName)
568
- )
569
- );
570
- const s3Subs = await Promise.all(
571
- s3Subscriptions.map(({ subscriptionName, topicName, queueName }) =>
572
- render('S3Subscription_Yaml.ejs', {
573
- subscriptionName,
574
- topicName,
575
- queueName
576
- })
577
- )
578
- );
579
- const parts = [chunks.join('\n')];
580
- if (wsSubs.length > 0) parts.push(wsSubs.join('\n'));
581
- if (s3Subs.length > 0) parts.push(s3Subs.join('\n'));
582
- const body = parts.filter(Boolean).join('\n');
583
- if (!body.trim()) return '';
584
- return `Resources:\n${indentYaml(body, 2)}`;
585
- }
586
-
587
- async function renderCrudInSqs(state) {
588
- return renderResourceGroup({
589
- ownTopics: state.crudOwnTopics,
590
- sqsQueues: state.crudSqsQueues
591
- });
592
- }
593
-
594
- async function renderRelationshipInSqs(state) {
595
- return renderResourceGroup({
596
- ownTopics: state.relationshipOwnTopics,
597
- sqsQueues: state.relationshipSqsQueues,
598
- dsqQueues: state.relationshipDsqQueues
599
- });
600
- }
601
-
602
- async function renderRbacInSqs(state) {
603
- return renderResourceGroup({
604
- ownTopics: state.rbacOwnTopics,
605
- sqsQueues: state.rbacSqsQueues
606
- });
607
- }
608
-
609
- async function renderCustomFlowInSqs(state) {
610
- return renderResourceGroup({
611
- ownTopics: state.customOwnTopics,
612
- sqsQueues: state.customSqsQueues,
613
- dsqQueues: state.customDsqQueues,
614
- s3Subscriptions: state.s3Subscriptions
615
- });
616
- }
617
-
618
- async function renderSystemInSqs(state) {
619
- return renderResourceGroup({
620
- ownTopics: state.systemOwnTopics,
621
- sqsQueues: state.systemSqsQueues,
622
- dsqQueues: state.systemDsqQueues,
623
- webSocketSubscriptions: state.webSocketSubscriptions
624
- });
625
- }
626
-
627
- async function renderOut(state) {
628
- const body = (
629
- await Promise.all(
630
- state.outTopics.map((topicName) =>
631
- render('FlowOut_Yaml.ejs', { topicName })
632
- )
633
- )
634
- ).join('\n');
635
- return `Resources:\n${indentYaml(body, 2)}`;
636
- }
637
-
638
- async function renderDynamo(state) {
639
- const [system, ...tables] = await Promise.all([
640
- render('SystemDynamoDB_Yaml.ejs', {
641
- hasS3: state.s3FlowTags.length > 0,
642
- hasRbac: state.selfRbac
643
- }),
644
- ...state.tables.map((table) => render('DynamoDBTable_Yaml.ejs', table))
645
- ]);
646
- const indentedTables = tables.map((t) => indentYaml(t, 2)).join('\n');
647
- return indentedTables ? `${system}\n${indentedTables}` : `${system}`;
648
- }
649
-
650
- async function renderIam(state) {
651
- const roles = ['ProcFindDataRole', 'RegisterRole'];
652
- if (state.crud) roles.push('FlowObjectsRole');
653
- if (state.relationships) roles.push('FlowRelationshipRole');
654
- if (state.selfRbac) roles.push('FlowRbacRole');
655
- if (state.websocket) roles.push('WebSocketMainRole');
656
- roles.push(...state.customRoles);
657
- const sortedRoles = roles.sort();
658
- return {
659
- iamContent: await render('SharedResource_Yaml.ejs', {
660
- roles: sortedRoles.map((roleName) => ({
661
- roleName,
662
- statements: roleStatements(state, roleName)
663
- }))
664
- }),
665
- roleNameConfigContent: renderRoleNameConfigYaml(sortedRoles)
666
- };
667
- }
668
-
669
- function getRbacValidatorServiceTags(state) {
670
- const tags = new Set();
671
- for (const obj of state.objects || []) {
672
- const targets = obj?.belongTo
673
- ? Array.isArray(obj.belongTo)
674
- ? obj.belongTo
675
- : [obj.belongTo]
676
- : [];
677
- for (const target of targets) {
678
- if (target?.serviceTag && target.serviceTag !== 'Rbac') {
679
- tags.add(target.serviceTag);
680
- }
681
- }
682
- if (
683
- obj.belongToSchema?.serviceTag &&
684
- obj.belongToSchema.serviceTag !== 'Rbac'
685
- ) {
686
- tags.add(obj.belongToSchema.serviceTag);
687
- }
688
- }
689
- return Array.from(tags).sort();
690
- }
691
-
692
- async function renderLambdaRoleConfig(state) {
693
- const s3Flows = state.flows
694
- .filter((flow) => includesEvent(flow, 's3'))
695
- .map((flow) => ({
696
- roleName: upperFirst(flow.flowTag)
697
- }));
698
- const rbacValidatorServiceTags = getRbacValidatorServiceTags(state);
699
- return render('GeneratedLambdaRoleServiceConfig.ejs', {
700
- crud: state.crud,
701
- relationships: state.relationships,
702
- rbac: state.rbac,
703
- rbacValidatorServiceTags,
704
- objectGraphServiceTags: state.objectGraphServiceTags,
705
- s3Flows
706
- });
707
- }
708
-
709
- function roleStatements(state, roleName) {
710
- const statements = [];
711
- if (roleNeedsSchemaRead(state, roleName)) {
712
- statements.push({
713
- action: ['s3:GetObject'],
714
- resource: ['arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}/*']
715
- });
716
- statements.push({
717
- action: ['s3:ListBucket'],
718
- resource: ['arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}']
719
- });
720
- }
721
- addStatement(statements, dynamoActionsForRole(roleName), [
722
- ...dynamoArns(roleTableNames(state, roleName)),
723
- ...externalDynamoArns(roleExternalDynamoTables(state, roleName))
724
- ]);
725
- addStatement(
726
- statements,
727
- SQS_CONSUMER_ACTIONS,
728
- queueArns(roleQueueNames(state, roleName))
729
- );
730
- addStatement(
731
- statements,
732
- SQS_DLQ_ACTIONS,
733
- queueArns(roleDlqQueueNames(state, roleName))
734
- );
735
- addStatement(
736
- statements,
737
- ['sns:Publish'],
738
- [
739
- ...topicArns(roleTopicNames(state, roleName)),
740
- ...roleExternalTopicArns(state, roleName)
741
- ]
742
- );
743
- addStatement(
744
- statements,
745
- ['lambda:InvokeFunction'],
746
- [
747
- ...roleExternalLambdaArns(state, roleName),
748
- ...roleHookLambdaArns(state, roleName)
749
- ]
750
- );
751
- if (state.webSocketRoles.has(roleName)) {
752
- statements.push({
753
- action: ['execute-api:ManageConnections'],
754
- resource: [
755
- 'arn:aws:execute-api:${self:custom.iz_region}:${self:custom.iz_accountId}:*/*'
756
- ]
757
- });
758
- }
759
- return statements;
760
- }
761
-
762
- function addStatement(statements, action, resource) {
763
- if (resource.length > 0) statements.push({ action, resource });
764
- }
765
-
766
- function roleNeedsSchemaRead(state, roleName) {
767
- if (!roleName.endsWith('Role') || !roleName) return false;
768
- return roleName !== 'FlowRbacRole' || state.selfRbac;
769
- }
770
-
771
- function roleTableNames(state, roleName) {
772
- if (roleName === 'RegisterRole') {
773
- if (state.websocket) return ['WebSocketTask', 'RegisterRecords'];
774
- return [];
775
- }
776
- if (roleName === 'WebSocketMainRole')
777
- return ['UserUploadRecords', 'WebSocketTask'];
778
- if (roleName === 'FlowObjectsRole')
779
- return [
780
- ...state.objectTableNames,
781
- 'AwaitingMultipleSteps',
782
- 'AwaitingMultipleStepByPending',
783
- 'WebSocketTask',
784
- ...(state.selfRbac
785
- ? [
786
- 'Roles',
787
- 'TargetRoles',
788
- 'RolePermissions',
789
- 'RoleUsers',
790
- 'UserRoles',
791
- 'RunningTotalUsage',
792
- 'RunningTotalReservedUsage'
793
- ]
794
- : [])
795
- ];
796
- if (roleName === 'FlowRelationshipRole')
797
- return [
798
- ...state.objectTableNames,
799
- ...state.relationshipTableNames,
800
- 'AwaitingMultipleSteps',
801
- 'AwaitingMultipleStepByPending',
802
- 'WebSocketTask'
803
- ];
804
- if (roleName === 'FlowRbacRole') return RBAC_TABLES;
805
- if (roleName === 'ProcFindDataRole')
806
- return [
807
- ...state.objectTableNames,
808
- ...state.relationshipTableNames,
809
- 'FindDataMain',
810
- 'LogicalResultsMain',
811
- 'LogicalResultsData'
812
- ];
813
- const flow = state.flows.find(
814
- (candidate) => customFlowRoleName(candidate?.flowTag ?? '') === roleName
815
- );
816
- if (!flow) return [];
817
- const tables = [];
818
- if (includesEvent(flow, 'ownTopic') || includesEvent(flow, 's3'))
819
- tables.push('WebSocketTask');
820
- if (includesEvent(flow, 's3'))
821
- tables.push('UserUploadRecords', 'ReservedDataMain');
822
- if (flow.statusType === 'storedCache') {
823
- const object = state.objectTablesByType.get(flow.objType?.objectType);
824
- if (object) tables.push(object);
825
- }
826
- return [...new Set(tables)];
827
- }
828
-
829
- function dynamoActionsForRole(roleName) {
830
- if (roleName === 'ProcFindDataRole')
831
- return [
832
- 'dynamodb:GetItem',
833
- 'dynamodb:PutItem',
834
- 'dynamodb:Query',
835
- 'dynamodb:UpdateItem'
836
- ];
837
- if (roleName === 'WebSocketMainRole')
838
- return ['dynamodb:DeleteItem', 'dynamodb:PutItem', 'dynamodb:Query'];
839
- if (roleName === 'FlowRbacRole' || roleName === 'FlowObjectsRole')
840
- return [...DYNAMO_ACTIONS, 'dynamodb:TransactWriteItems'];
841
- return DYNAMO_ACTIONS;
842
- }
843
-
844
- function roleQueueNames(state, roleName) {
845
- if (roleName === 'ProcFindDataRole') {
846
- return LEGACY_PROCESS_QUEUES;
847
- }
848
- if (roleName === 'WebSocketMainRole') {
849
- if (state.websocket) {
850
- return ['WebSocketCompleteHdrSqs'];
851
- }
852
- return [];
853
- }
854
- if (roleName === 'RegisterRole') {
855
- if (state.websocket) {
856
- return ['RegisterCompleteHdrSqs'];
857
- }
858
- return [];
859
- }
860
- if (roleName === 'FlowObjectsRole') {
861
- return CRUD_ACTIONS.flatMap((action) => {
862
- const plan = state.crudActionPlans[action];
863
- if (!plan.outputTopic) return [];
864
- return [
865
- `${action}HdrSqs`,
866
- ...(plan.hasCreateNodeCallback ? [`${action}NodeCallbackHdrSqs`] : []),
867
- ...(plan.hasBeforeHookCallback ? [`${action}BeforeHookCallbackHdrSqs`] : []),
868
- ...(plan.hasAfterHookCallback ? [`${action}AfterHookCallbackHdrSqs`] : []),
869
- ...(plan.hasRbacCallback ? [`${action}RbacCallbackHdrSqs`] : []),
870
- ...(plan.hasComplete ? [`${action}CompleteHdrSqs`] : [])
871
- ];
872
- });
873
- }
874
- if (roleName === 'FlowRelationshipRole') {
875
- return [
876
- ...RELATIONSHIP_ACTIONS.flatMap((action) => [
877
- `${action}HdrSqs`,
878
- `${action}CompleteHdrSqs`
879
- ]),
880
- 'GetRelationshipHdrDsq'
881
- ];
882
- }
883
- if (roleName === 'FlowRbacRole') {
884
- return [
885
- ...RBAC_ACTIONS.map((action) => `${action}HdrSqs`),
886
- ...rbacFlowQueueNames(state)
887
- ];
888
- }
889
- const flow = state.flows.find(
890
- (candidate) => customFlowRoleName(candidate?.flowTag ?? '') === roleName
891
- );
892
- if (!flow) {
893
- return [];
894
- }
895
- const tag = upperFirst(flow.flowTag);
896
- const queueList = [];
897
- if (includesEvent(flow, 'extTopic')) {
898
- queueList.push(`${tag}HdrSqs`);
899
- }
900
- if (includesEvent(flow, 'ownTopic')) {
901
- queueList.push(`${tag}HdrSqs`);
902
- }
903
- if (includesEvent(flow, 'dsq')) {
904
- queueList.push(`${tag}HdrDsq`);
905
- }
906
- if (flowHasRbac(flow, state.objects)) {
907
- queueList.push(`${tag}RbacCallbackHdrSqs`);
908
- }
909
- queueList.push(
910
- ...flowExtTopicSteps([flow]).map(
911
- ({ flowTag, stepName }) => `${flowTag}${stepName}HdrSqs`
912
- )
913
- );
914
- queueList.push(
915
- ...flowDsqSteps([flow]).map(
916
- ({ flowTag, stepName }) => `${flowTag}${stepName}HdrDsq`
917
- )
918
- );
919
- queueList.push(
920
- ...state.sqsQueues.filter(
921
- (queueName) => includesEvent(flow, 's3') && queueName.startsWith(tag)
922
- )
923
- );
924
- if (flow.outputTopic) {
925
- queueList.push(`${tag}CompleteHdrSqs`);
926
- }
927
- return queueList;
928
- }
929
-
930
- function roleDlqQueueNames(state, roleName) {
931
- const queues = roleQueueNames(state, roleName);
932
- if (!Array.isArray(queues) || queues.length === 0) {
933
- return [];
934
- }
935
- return queues.map((q) => (q.endsWith('DLQ') ? q : `${q}DLQ`));
936
- }
937
-
938
- function roleTopicNames(state, roleName) {
939
- if (roleName === 'ProcFindDataRole')
940
- return state.outTopics
941
- .filter((topic) => topic === 'FindData')
942
- .map((t) => `${t}_Out`);
943
- if (roleName === 'FlowObjectsRole') {
944
- const actions = state.outTopics.filter((topic) =>
945
- CRUD_ACTIONS.includes(topic)
946
- );
947
- const topics = [];
948
- for (const action of actions) {
949
- if (action === 'Get') {
950
- topics.push(`${action}_Out`);
951
- } else {
952
- topics.push(`${action}_In`, `${action}_Out`);
953
- }
954
- }
955
- if (state.rbac) {
956
- topics.push('ValidatorRbac_In');
957
- }
958
- for (const action of ['Create', 'Update', 'Delete']) {
959
- const plan = state.crudActionPlans?.[action];
960
- if (plan?.hasBeforeHookAsync) {
961
- for (const hook of plan.beforeHooks) {
962
- if (hook?.generatedCodeConfig?.invocationType === 'ownTopic') {
963
- topics.push(`${upperFirst(hook.flowTag)}_In`);
964
- }
965
- }
966
- }
967
- if (plan?.hasAfterHookAsync) {
968
- for (const hook of plan.afterHooks) {
969
- if (hook?.generatedCodeConfig?.invocationType === 'ownTopic') {
970
- topics.push(`${upperFirst(hook.flowTag)}_In`);
971
- }
972
- }
973
- }
974
- }
975
- return [...new Set(topics)];
976
- }
977
- if (roleName === 'FlowRelationshipRole') {
978
- const actions = state.outTopics.filter((topic) =>
979
- RELATIONSHIP_ACTIONS.includes(topic)
980
- );
981
- const topics = [];
982
- for (const action of actions) {
983
- if (action === 'GetRelationship') {
984
- topics.push(`${action}_Out`);
985
- } else {
986
- topics.push(`${action}_In`, `${action}_Out`);
987
- }
988
- }
989
- if (state.rbac) {
990
- topics.push('ValidatorRbac_In');
991
- }
992
- return topics;
993
- }
994
- if (roleName === 'FlowRbacRole') {
995
- const actions = state.outTopics.filter((topic) =>
996
- RBAC_ACTIONS.includes(topic)
997
- );
998
- const topics = [];
999
- for (const action of actions) {
1000
- if (action.startsWith('List')) {
1001
- topics.push(`${action}_Out`);
1002
- } else {
1003
- topics.push(`${action}_In`, `${action}_Out`);
1004
- }
1005
- }
1006
- return topics;
1007
- }
1008
- const flow = state.flows.find(
1009
- (candidate) => customFlowRoleName(candidate?.flowTag ?? '') === roleName
1010
- );
1011
- if (flow && flow.flowTag) {
1012
- const name = upperFirst(flow.flowTag);
1013
- const topics = [];
1014
- if (state.outTopics.includes(name)) {
1015
- topics.push(`${name}_Out`);
1016
- }
1017
- if (includesEvent(flow, 'ownTopic')) {
1018
- topics.push(`${name}_In`);
1019
- }
1020
- return topics;
1021
- }
1022
- return [];
1023
- }
1024
-
1025
- function dynamoArns(tableNames) {
1026
- return [...new Set(tableNames)]
1027
- .sort()
1028
- .map(
1029
- (tableName) =>
1030
- `arn:aws:dynamodb:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:table/${'${self:custom.iz_resourcePrefix}'}${tableName}`
1031
- );
1032
- }
1033
-
1034
- /**
1035
- * Generates DynamoDB ARNs for tables that belong to a parent service
1036
- * (via extendObjType). Uses the parent serviceTag instead of the
1037
- * current service's resourcePrefix, because those tables are owned
1038
- * by a different CloudFormation stack.
1039
- *
1040
- * ARN format:
1041
- * ...table/{parentServiceTag}_{iz_stage}_{tableName}
1042
- */
1043
- function externalDynamoArns(externalTables) {
1044
- const seen = new Set();
1045
- const unique = [];
1046
- for (const table of externalTables) {
1047
- const dedupeKey = `${table.serviceTag}::${table.tableName}`;
1048
- if (!seen.has(dedupeKey)) {
1049
- seen.add(dedupeKey);
1050
- unique.push(table);
1051
- }
1052
- }
1053
- return unique
1054
- .sort((a, b) =>
1055
- `${a.serviceTag}_${a.tableName}`.localeCompare(
1056
- `${b.serviceTag}_${b.tableName}`
1057
- )
1058
- )
1059
- .map(
1060
- ({ serviceTag, tableName }) =>
1061
- `arn:aws:dynamodb:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:table/${serviceTag}${'${self:custom.iz_stage}'}${tableName}`
1062
- );
1063
- }
1064
-
1065
- /**
1066
- * Returns the external DynamoDB table references (cross-service)
1067
- * that should be accessible from a given IAM role.
1068
- */
1069
- function roleExternalDynamoTables(state, roleName) {
1070
- if (
1071
- roleName === 'FlowObjectsRole' ||
1072
- roleName === 'FlowRelationshipRole' ||
1073
- roleName === 'ProcFindDataRole'
1074
- ) {
1075
- return state.externalDynamoTables;
1076
- }
1077
- return [];
1078
- }
1079
-
1080
- function roleExternalTopicArns(state, roleName) {
1081
- if (!state.rbac) {
1082
- return [];
1083
- }
1084
- const extraTags = getRbacValidatorServiceTags(state).filter(
1085
- (tag) => tag && tag !== 'Rbac'
1086
- );
1087
- const extraTopics = extraTags.map(
1088
- (tag) =>
1089
- `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:${tag}_\${self:custom.iz_stage}_ValidatorRbac_In`
1090
- );
1091
- if (roleName === 'FlowObjectsRole') {
1092
- return [
1093
- `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ValidatorRbac_In`,
1094
- `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ConfirmUsage_In`,
1095
- `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_CancelUsage_In`,
1096
- ...extraTopics
1097
- ];
1098
- }
1099
- if (roleName === 'FlowRbacRole' || roleName === 'FlowRelationshipRole') {
1100
- return [
1101
- `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ValidatorRbac_In`,
1102
- ...extraTopics
1103
- ];
1104
- }
1105
- return [];
1106
- }
1107
-
1108
- function roleExternalLambdaArns(state, roleName) {
1109
- if (!state.rbac) {
1110
- return [];
1111
- }
1112
- const extraTags = getRbacValidatorServiceTags(state).filter(
1113
- (tag) => tag && tag !== 'Rbac'
1114
- );
1115
- const extraLambdas = extraTags.map(
1116
- (tag) =>
1117
- `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:${tag}\${self:custom.iz_stage}ValidatorRbacHdrInv`
1118
- );
1119
- if (roleName === 'FlowObjectsRole') {
1120
- return [
1121
- `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}ValidatorRbacHdrInv`,
1122
- `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}ConfirmUsageHdrInv`,
1123
- `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}CancelUsageHdrInv`,
1124
- ...extraLambdas
1125
- ];
1126
- }
1127
- if (
1128
- roleName === 'FlowRbacRole' ||
1129
- roleName === 'FlowRelationshipRole'
1130
- ) {
1131
- return [
1132
- `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}ValidatorRbacHdrInv`,
1133
- ...extraLambdas
1134
- ];
1135
- }
1136
- return [];
1137
- }
1138
-
1139
- function roleHookLambdaArns(state, roleName) {
1140
- if (roleName !== 'FlowObjectsRole') {
1141
- return [];
1142
- }
1143
- const hookLambdas = [];
1144
- for (const action of ['Create', 'Update', 'Delete']) {
1145
- const plan = state.crudActionPlans?.[action];
1146
- if (plan?.hasBeforeHookSync) {
1147
- for (const hook of plan.beforeHooks) {
1148
- if (hook?.generatedCodeConfig?.invocationType === 'lambdaSyncInv') {
1149
- const hookTag = upperFirst(hook.flowTag);
1150
- hookLambdas.push(
1151
- `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:\${self:custom.iz_resourcePrefix}${hookTag}*`
1152
- );
1153
- }
1154
- }
1155
- }
1156
- if (plan?.hasAfterHookSync) {
1157
- for (const hook of plan.afterHooks) {
1158
- if (hook?.generatedCodeConfig?.invocationType === 'lambdaSyncInv') {
1159
- const hookTag = upperFirst(hook.flowTag);
1160
- hookLambdas.push(
1161
- `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:\${self:custom.iz_resourcePrefix}${hookTag}*`
1162
- );
1163
- }
1164
- }
1165
- }
1166
- }
1167
- return [...new Set(hookLambdas)];
1168
- }
1169
-
1170
- function queueArns(queueNames) {
1171
- return [...new Set(queueNames)]
1172
- .sort()
1173
- .map(
1174
- (queueName) =>
1175
- `arn:aws:sqs:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:${'${self:custom.iz_resourcePrefix}'}${queueName}`
1176
- );
1177
- }
1178
-
1179
- function topicArns(topicNames) {
1180
- return [...new Set(topicNames)]
1181
- .sort()
1182
- .map(
1183
- (topic) =>
1184
- `arn:aws:sns:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:${'${self:custom.iz_serviceTag}'}_${'${self:custom.iz_stage}'}_${topic}`
1185
- );
1186
- }
1187
-
1188
- function renderWebSocketSubscription(topicName) {
1189
- return render('WebSocketSubscription_Yaml.ejs', { topicName });
1190
- }
1191
-
1192
- function collectTables(schemas) {
1193
- const tables = new Map();
1194
- for (const schema of schemas) {
1195
- if (!schema?.storageResources) continue;
1196
- const identifiers = Array.isArray(schema.identifiers)
1197
- ? schema.identifiers
1198
- : [];
1199
- const pkIdentifier = identifiers.find(
1200
- (identifier) =>
1201
- identifier.type === 'partitionKey' || identifier.type === 'identifier'
1202
- );
1203
- const skIdentifier = identifiers.find(
1204
- (identifier) => identifier.type === 'sortKey'
1205
- );
1206
- const pk = keyName(pkIdentifier) ?? 'PK';
1207
- const sk = keyName(skIdentifier);
1208
- const pkType = keyAttributeType(pkIdentifier, schema);
1209
- const skType = keyAttributeType(skIdentifier, schema);
1210
- for (const resource of Object.values(schema.storageResources)) {
1211
- if (
1212
- (resource?.storageType === 'dynamo' ||
1213
- resource?.storageType === 'dynamoDB' ||
1214
- !resource?.storageType) &&
1215
- resource.tableName &&
1216
- !tables.has(resource.tableName)
1217
- )
1218
- tables.set(resource.tableName, { pk, sk, pkType, skType });
1219
- }
1220
- }
1221
- return tables;
1222
- }
1223
-
1224
- function keyAttributeType(identifier, schema) {
1225
- if (!identifier) return 'S';
1226
- if (identifier.attributeType) {
1227
- return identifier.attributeType;
1228
- }
1229
- if (identifier.dataType) {
1230
- return mapTypeToAttributeType(identifier.dataType);
1231
- }
1232
- if (identifier.fieldType) {
1233
- return mapTypeToAttributeType(identifier.fieldType);
1234
- }
1235
- const fieldName = identifier.fieldName ?? identifier.name;
1236
- if (fieldName && schema) {
1237
- let fieldDef =
1238
- schema.fieldNames?.[fieldName] ?? schema.properties?.[fieldName];
1239
- if (!fieldDef && Array.isArray(schema.addOnDataStructure)) {
1240
- for (const addOn of schema.addOnDataStructure) {
1241
- if (addOn?.fieldNames?.[fieldName]) {
1242
- fieldDef = addOn.fieldNames[fieldName];
1243
- break;
1244
- }
1245
- }
1246
- }
1247
- if (fieldDef?.attributeType) {
1248
- return fieldDef.attributeType;
1249
- }
1250
- const type = fieldDef?.type ?? fieldDef?.dataType;
1251
- if (type) {
1252
- return mapTypeToAttributeType(type);
1253
- }
1254
- }
1255
- return 'S';
1256
- }
1257
-
1258
- function mapTypeToAttributeType(type) {
1259
- if (typeof type !== 'string') return 'S';
1260
- const lower = type.toLowerCase();
1261
- if (
1262
- lower === 'number' ||
1263
- lower === 'numeric' ||
1264
- lower === 'integer' ||
1265
- lower === 'float' ||
1266
- lower === 'int' ||
1267
- lower === 'double' ||
1268
- lower === 'n'
1269
- ) {
1270
- return 'N';
1271
- }
1272
- if (lower === 'binary' || lower === 'b') {
1273
- return 'B';
1274
- }
1275
- return 'S';
1276
- }
1277
-
1278
- function keyName(identifier) {
1279
- if (!identifier) return null;
1280
- return (
1281
- identifier.name ??
1282
- identifier.fieldName ??
1283
- (Array.isArray(identifier.fieldNames)
1284
- ? identifier.fieldNames.join(identifier.deliminator ?? '_')
1285
- : null)
1286
- );
1287
- }
1288
-
1289
- const VALID_CRUD_GENERATED_BY = new Set([
1290
- 'user',
1291
- 'systemWithUser',
1292
- 'systemNoUser'
1293
- ]);
1294
-
1295
- function isCrudObject(object) {
1296
- return VALID_CRUD_GENERATED_BY.has(object?.generatedBy);
1297
- }
1298
- function hasSelfRbacAddOn(object) {
1299
- return (
1300
- Array.isArray(object?.addOnDataStructure) &&
1301
- object.addOnDataStructure.some((addOn) => addOn?.type === 'rbac')
1302
- );
1303
- }
1304
-
1305
- function hasRbacAddOn(object, objects = []) {
1306
- const selfHasRbac = hasSelfRbacAddOn(object);
1307
- const belongToTargets = object?.belongTo
1308
- ? Array.isArray(object.belongTo)
1309
- ? object.belongTo
1310
- : [object.belongTo]
1311
- : [];
1312
- const belongToHasRbac =
1313
- (Array.isArray(object?.belongToSchema?.addOnDataStructure) &&
1314
- object.belongToSchema.addOnDataStructure.some(
1315
- (addOn) => addOn?.type === 'rbac'
1316
- )) ||
1317
- belongToTargets.some((target) => {
1318
- if (target?.serviceTag === 'UserAccount') return true;
1319
- const matched = objects.find(
1320
- (candidate) =>
1321
- candidate?.serviceTag === target?.serviceTag &&
1322
- candidate?.objectType === target?.objectType
1323
- );
1324
- return hasSelfRbacAddOn(matched);
1325
- });
1326
- return selfHasRbac || belongToHasRbac;
1327
- }
1328
-
1329
- /**
1330
- * Returns the per-object RBAC flow queues (e.g. UserRbacFlowHdrSqs)
1331
- * created for objects with an 'rbac' add-on. These are consumed by the
1332
- * UserRbacFlow SQS functions emitted in emitRbacApplicationArtifacts.
1333
- */
1334
- function rbacFlowQueueNames(state) {
1335
- const flowTags = [
1336
- ...new Set((state.objects ?? []).filter(hasSelfRbacAddOn).map(rbacFlowTag))
1337
- ];
1338
- return flowTags.map((flowTag) => `${flowTag}HdrSqs`);
1339
- }
1340
- function includesEvent(flow, event) {
1341
- return Array.isArray(flow?.event) && flow.event.includes(event);
1342
- }
1343
- function upperFirst(value) {
1344
- return typeof value === 'string' && value
1345
- ? value[0].toUpperCase() + value.slice(1)
1346
- : '';
1347
- }
1348
- function normalizeYaml(content) {
1349
- return `${content.trimEnd()}\n`;
1350
- }
1351
- function renderCorsYaml() {
128
+ export function renderCorsYaml() {
1352
129
  return `allowedOrigins:
1353
130
  - '*'
1354
131
  allowedHeaders:
@@ -1366,18 +143,3 @@ maxAge: 0
1366
143
  allowCredentials: false
1367
144
  `;
1368
145
  }
1369
- function renderRoleNameConfigYaml(roles) {
1370
- return [
1371
- '# for createIamRole',
1372
- ...roles.map(
1373
- (roleName) =>
1374
- `- arn:aws:iam::\${self:custom.iz_accountId}:role/\${self:custom.iz_resourcePrefix}${roleName}`
1375
- )
1376
- ].join('\n');
1377
- }
1378
- async function render(templateName, data) {
1379
- return ejsLib.render(
1380
- await readFile(new URL(templateName, TEMPLATE_URL), 'utf8'),
1381
- { resourceQueueName: data.queueName, ...data }
1382
- );
1383
- }