@izara_project/izara-core-generate-service-code 1.0.66 → 1.0.67

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 (27) hide show
  1. package/docs/TriggeredCache Flow.drawio +193 -0
  2. package/docs/test.mmd +47 -0
  3. package/docs/test.png +0 -0
  4. package/package.json +2 -2
  5. package/src/compile/loadSchemas.js +23 -1
  6. package/src/emit/crud/emitCrudApplicationArtifacts.js +9 -5
  7. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +5 -5
  8. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +5 -5
  9. package/src/emit/crud/templates/FlowEndpointComplete_Main.ejs +25 -4
  10. package/src/emit/crud/templates/FlowHandler_HdrApi.ejs +18 -0
  11. package/src/emit/crud/templates/GetEndpoint_Main.ejs +2 -2
  12. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +10 -8
  13. package/src/emit/custom/customFlows.js +76 -25
  14. package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +92 -75
  15. package/src/emit/custom/templates/FlowEndpoint_Main.ejs +4 -4
  16. package/src/emit/custom/templates/triggerCache/CheckTriggerCache_Main.ejs +1 -1
  17. package/src/emit/function/functionYamlArtifact.js +26 -12
  18. package/src/emit/rbac/emitRbacApplicationArtifacts.js +21 -7
  19. package/src/emit/relationship/emitRelationshipApplicationArtifacts.js +6 -5
  20. package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +2 -2
  21. package/src/emit/resource/emitResourceArtifacts.js +400 -105
  22. package/src/emit/system/emitSystemWebSocketArtifacts.js +2 -1
  23. package/src/generate.js +2 -2
  24. package/test/crudApplicationArtifacts.test.js +1 -1
  25. package/test/customFlowApplicationArtifacts.test.js +37 -34
  26. package/test/publicApi.test.js +9 -9
  27. package/test/resourceArtifacts.test.js +1 -1
@@ -1,22 +1,84 @@
1
1
  import ejs from 'ejs';
2
2
  import { readFile } from 'node:fs/promises';
3
3
 
4
- import { customFlowRoleName, flowExtTopicSteps, generatedOwnTopicFlows, supportedCustomFlows, triggerCacheFlows, triggerCacheQueueNames } from '../custom/customFlows.js';
4
+ import {
5
+ customFlowRoleName,
6
+ flowDsqSteps,
7
+ flowExtTopicSteps,
8
+ generatedOwnTopicFlows,
9
+ supportedCustomFlows
10
+ } from '../custom/customFlows.js';
5
11
  import { hasSystemWebSocket } from '../system/emitSystemWebSocketArtifacts.js';
6
12
 
7
13
  const TEMPLATE_URL = new URL('./templates/', import.meta.url);
8
14
  const RESOURCE_ROOT = 'resource/sls_yaml/generatedCode/source';
9
15
  const APP_ROOT = 'app/sls_yaml/generatedCode/source';
10
16
  const CRUD_ACTIONS = ['Create', 'Update', 'Delete', 'Get'];
11
- const RELATIONSHIP_ACTIONS = ['CreateRelationship', 'UpdateRelationship', 'DeleteRelationship', 'GetRelationship', 'ChangeRelationship', 'MoveRelationship'];
12
- const RBAC_ACTIONS = ['CreateTargetRole', 'ListTargetRole', 'DeleteTargetRole', 'CreateRolePermissions', 'ListRolePermissions', 'DeleteRolePermissions', 'CreateUserRole', 'ListUserInRoles', 'DeleteUserFromRole'];
13
- const SYSTEM_TABLES = ['WebSocketTask', 'UserUploadRecords', 'RegisterRecords', 'AwaitingMultipleSteps', 'AwaitingMultipleStepByPending', 'FindDataMain', 'LogicalResultsMain', 'LogicalResultsData', 'AwaitingStep', 'TargetRoles', 'RolePermissions', 'RoleUsers', 'UserRoles'];
14
- const RBAC_TABLES = ['TargetRoles', 'RolePermissions', 'RoleUsers', 'UserRoles', 'WebSocketTask'];
15
- const LEGACY_PROCESS_QUEUES = ['FindDataHdrSqs', 'ProcessLogicalHdrSqs', 'PaginateProcessLogicalHdrDsq', 'PaginateProcessLogicalHdrSqs'];
16
- const DYNAMO_ACTIONS = ['dynamodb:DeleteItem', 'dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:Query', 'dynamodb:UpdateItem'];
17
- const SQS_CONSUMER_ACTIONS = ['sqs:DeleteMessage', 'sqs:GetQueueAttributes', 'sqs:ReceiveMessage'];
17
+ const RELATIONSHIP_ACTIONS = [
18
+ 'CreateRelationship',
19
+ 'UpdateRelationship',
20
+ 'DeleteRelationship',
21
+ 'GetRelationship',
22
+ 'ChangeRelationship',
23
+ 'MoveRelationship'
24
+ ];
25
+ const RBAC_ACTIONS = [
26
+ 'CreateTargetRole',
27
+ 'ListTargetRole',
28
+ 'DeleteTargetRole',
29
+ 'CreateRolePermissions',
30
+ 'ListRolePermissions',
31
+ 'DeleteRolePermissions',
32
+ 'CreateUserRole',
33
+ 'ListUserInRoles',
34
+ 'DeleteUserFromRole'
35
+ ];
36
+ const SYSTEM_TABLES = [
37
+ 'WebSocketTask',
38
+ 'UserUploadRecords',
39
+ 'RegisterRecords',
40
+ 'AwaitingMultipleSteps',
41
+ 'AwaitingMultipleStepByPending',
42
+ 'FindDataMain',
43
+ 'LogicalResultsMain',
44
+ 'LogicalResultsData',
45
+ 'AwaitingStep',
46
+ 'TargetRoles',
47
+ 'RolePermissions',
48
+ 'RoleUsers',
49
+ 'UserRoles'
50
+ ];
51
+ const RBAC_TABLES = [
52
+ 'TargetRoles',
53
+ 'RolePermissions',
54
+ 'RoleUsers',
55
+ 'UserRoles',
56
+ 'WebSocketTask'
57
+ ];
58
+ const LEGACY_PROCESS_QUEUES = [
59
+ 'FindDataHdrSqs',
60
+ 'ProcessLogicalHdrSqs',
61
+ 'PaginateProcessLogicalHdrDsq',
62
+ 'PaginateProcessLogicalHdrSqs'
63
+ ];
64
+ const DYNAMO_ACTIONS = [
65
+ 'dynamodb:DeleteItem',
66
+ 'dynamodb:GetItem',
67
+ 'dynamodb:PutItem',
68
+ 'dynamodb:Query',
69
+ 'dynamodb:UpdateItem'
70
+ ];
71
+ const SQS_CONSUMER_ACTIONS = [
72
+ 'sqs:DeleteMessage',
73
+ 'sqs:GetQueueAttributes',
74
+ 'sqs:ReceiveMessage'
75
+ ];
18
76
 
19
- export async function emitResourceArtifacts({ objects, relationships, flows = [] }) {
77
+ export async function emitResourceArtifacts({
78
+ objects,
79
+ relationships,
80
+ flows = []
81
+ }) {
20
82
  if (![objects, relationships, flows].every(Array.isArray)) {
21
83
  throw new TypeError('objects, relationships, and flows must be arrays');
22
84
  }
@@ -28,10 +90,22 @@ export async function emitResourceArtifacts({ objects, relationships, flows = []
28
90
  renderIam(state)
29
91
  ]);
30
92
  return [
31
- { path: `${RESOURCE_ROOT}/generated-sns-in-sqs.yml`, content: normalizeYaml(inSqs) },
32
- { path: `${RESOURCE_ROOT}/generated-sns-out.yml`, content: normalizeYaml(out) },
33
- { path: `${RESOURCE_ROOT}/generated-dynamoDB-table.yml`, content: normalizeYaml(dynamo) },
34
- { path: `${APP_ROOT}/generate-shared-resource.yml`, content: normalizeYaml(iam) }
93
+ {
94
+ path: `${RESOURCE_ROOT}/generated-sns-in-sqs.yml`,
95
+ content: normalizeYaml(inSqs)
96
+ },
97
+ {
98
+ path: `${RESOURCE_ROOT}/generated-sns-out.yml`,
99
+ content: normalizeYaml(out)
100
+ },
101
+ {
102
+ path: `${RESOURCE_ROOT}/generated-dynamoDB-table.yml`,
103
+ content: normalizeYaml(dynamo)
104
+ },
105
+ {
106
+ path: `${APP_ROOT}/generate-shared-resource.yml`,
107
+ content: normalizeYaml(iam)
108
+ }
35
109
  ];
36
110
  }
37
111
 
@@ -45,27 +119,41 @@ export const resourceArtifactPaths = [
45
119
  function createResourceState({ objects, relationships, flows }) {
46
120
  const crud = objects.some(isCrudObject);
47
121
  const rbac = objects.some(hasRbacAddOn);
48
- const ownTopics = new Set(flows.filter(flow => includesEvent(flow, 'ownTopic')).map(flow => upperFirst(flow.flowTag)));
122
+ const ownTopics = new Set(
123
+ flows
124
+ .filter(flow => includesEvent(flow, 'ownTopic'))
125
+ .map(flow => upperFirst(flow.flowTag))
126
+ );
49
127
  const sqsQueues = new Set([
50
128
  ...LEGACY_PROCESS_QUEUES,
51
- ...flows.filter(flow => includesEvent(flow, 'extTopic')).map(flow => `Process${upperFirst(flow.flowTag)}HdrSqs`)
129
+ ...flows
130
+ .filter(flow => includesEvent(flow, 'extTopic'))
131
+ .map(flow => `Process${upperFirst(flow.flowTag)}HdrSqs`)
52
132
  ]);
53
- const triggerCache = triggerCacheFlows(flows);
54
- const triggerCacheQueues = [...new Set(triggerCache.flatMap(triggerCacheQueueNames))].map(queueName => ({
55
- queueName,
56
- resourceQueueName: queueName.replaceAll('_', '')
57
- }));
58
- flowExtTopicSteps(flows).forEach(({ flowTag, stepName }) => sqsQueues.add(`${flowTag}${stepName}HdrSqs`));
59
- flows.filter(flow => includesEvent(flow, 's3')).forEach(flow => {
60
- const tag = upperFirst(flow.flowTag);
61
- sqsQueues.add(`${tag}CreatePreSignUrlHdrSqs`);
62
- sqsQueues.add(`${tag}ReservedLimitHdrSqs`);
63
- sqsQueues.add(`${tag}ConfirmAfterUploadHdrSqs`);
64
- });
65
- const dsqQueues = new Set(flows.filter(flow => includesEvent(flow, 'dsq')).map(flow => `Process${upperFirst(flow.flowTag)}HdrDsq`));
133
+ flowExtTopicSteps(flows).forEach(({ flowTag, stepName }) =>
134
+ sqsQueues.add(`${flowTag}${stepName}HdrSqs`)
135
+ );
136
+ flows
137
+ .filter(flow => includesEvent(flow, 's3'))
138
+ .forEach(flow => {
139
+ const tag = upperFirst(flow.flowTag);
140
+ sqsQueues.add(`${tag}CreatePreSignUrlHdrSqs`);
141
+ sqsQueues.add(`${tag}ReservedLimitHdrSqs`);
142
+ sqsQueues.add(`${tag}ConfirmAfterUploadHdrSqs`);
143
+ });
144
+ const dsqQueues = new Set(
145
+ flows
146
+ .filter(flow => includesEvent(flow, 'dsq'))
147
+ .map(flow => `${upperFirst(flow.flowTag)}HdrDsq`)
148
+ );
149
+ flowDsqSteps(flows).forEach(({ flowTag, stepName }) =>
150
+ dsqQueues.add(`${flowTag}${stepName}HdrDsq`)
151
+ );
66
152
  if (crud) {
67
153
  CRUD_ACTIONS.forEach(action => ownTopics.add(action));
68
- ['Create', 'Update', 'Delete'].forEach(action => sqsQueues.add(`${action}CompleteHdrSqs`));
154
+ ['Create', 'Update', 'Delete'].forEach(action =>
155
+ sqsQueues.add(`${action}CompleteHdrSqs`)
156
+ );
69
157
  }
70
158
  if (relationships.length > 0) {
71
159
  RELATIONSHIP_ACTIONS.forEach(action => {
@@ -79,75 +167,136 @@ function createResourceState({ objects, relationships, flows }) {
79
167
  ownTopics.add(action);
80
168
  sqsQueues.add(`Process${action}HdrSqs`);
81
169
  });
82
- objects.filter(hasRbacAddOn).forEach(object => sqsQueues.add(`Process${upperFirst(object.objectType)}RbacFlowHdrInv`));
170
+ objects
171
+ .filter(hasRbacAddOn)
172
+ .forEach(object =>
173
+ sqsQueues.add(`Process${upperFirst(object.objectType)}RbacFlowHdrInv`)
174
+ );
83
175
  }
84
176
  const beforeLogicalByAction = new Map();
85
- for (const flow of flows.filter(flow => flow?.generatedCodeConfig?.codeHookTag === 'beforeLogical')) {
177
+ for (const flow of flows.filter(
178
+ flow => flow?.generatedCodeConfig?.codeHookTag === 'beforeLogical'
179
+ )) {
86
180
  const action = upperFirst(flow.generatedCodeConfig.generatedCodeTag);
87
181
  if (!action || !flow.flowTag) continue;
88
182
  const flowTags = beforeLogicalByAction.get(action) ?? new Set();
89
183
  flowTags.add(flow.flowTag);
90
184
  beforeLogicalByAction.set(action, flowTags);
91
185
  }
92
- const beforeLogical = [...beforeLogicalByAction].map(([action, flowTags]) => ({ action, flowTags: [...flowTags].sort() }));
93
- const customRoles = [...new Set([
94
- ...supportedCustomFlows(flows).map(flow => customFlowRoleName(flow.flowTag)),
95
- ...generatedOwnTopicFlows(flows).map(flow => customFlowRoleName(flow.flowTag)),
96
- ...flowExtTopicSteps(flows).map(({ flow }) => customFlowRoleName(flow.flowTag)),
97
- ...triggerCache.map(flow => customFlowRoleName(flow.flowTag))
98
- ])];
99
- const websocket = hasSystemWebSocket(flows, { includeRelationshipOwnTopic: relationships.length > 0 });
186
+ const beforeLogical = [...beforeLogicalByAction].map(
187
+ ([action, flowTags]) => ({ action, flowTags: [...flowTags].sort() })
188
+ );
189
+ const customRoles = [
190
+ ...new Set([
191
+ ...supportedCustomFlows(flows).map(flow =>
192
+ customFlowRoleName(flow.flowTag)
193
+ ),
194
+ ...generatedOwnTopicFlows(flows).map(flow =>
195
+ customFlowRoleName(flow.flowTag)
196
+ ),
197
+ ...flowExtTopicSteps(flows).map(({ flow }) =>
198
+ customFlowRoleName(flow.flowTag)
199
+ ),
200
+ ...flowDsqSteps(flows).map(({ flow }) =>
201
+ customFlowRoleName(flow.flowTag)
202
+ )
203
+ ])
204
+ ];
205
+ const websocket = hasSystemWebSocket(flows, {
206
+ includeRelationshipOwnTopic: true
207
+ });
100
208
  const webSocketRoles = new Set(websocket ? ['WebSocketMainRole'] : []);
101
209
  if (crud) webSocketRoles.add('FlowObjectsRole');
102
210
  if (relationships.length > 0) webSocketRoles.add('FlowRelationshipRole');
103
211
  if (rbac) webSocketRoles.add('FlowRbacRole');
104
212
  [...supportedCustomFlows(flows), ...generatedOwnTopicFlows(flows)]
105
- .filter(flow => includesEvent(flow, 'ownTopic') || includesEvent(flow, 's3'))
213
+ .filter(
214
+ flow => includesEvent(flow, 'ownTopic') || includesEvent(flow, 's3')
215
+ )
106
216
  .forEach(flow => webSocketRoles.add(customFlowRoleName(flow.flowTag)));
107
217
  const outTopics = new Set([
108
218
  ...ownTopics,
109
219
  ...beforeLogical.map(({ action }) => action),
110
- ...flows.filter(flow => flow?.outputTopic && upperFirst(flow.flowTag)).map(flow => upperFirst(flow.flowTag))
220
+ ...flows
221
+ .filter(flow => flow?.outputTopic && upperFirst(flow.flowTag))
222
+ .map(flow => upperFirst(flow.flowTag))
111
223
  ]);
112
224
  const objectTableNames = [...collectTables(objects).keys()];
113
225
  const relationshipTableNames = [...collectTables(relationships).keys()];
114
- const tableMap = new Map([...collectTables(objects), ...collectTables(relationships)]);
226
+ const tableMap = new Map([
227
+ ...collectTables(objects),
228
+ ...collectTables(relationships)
229
+ ]);
115
230
  return {
116
231
  crud,
117
232
  rbac,
118
233
  relationships: relationships.length > 0,
119
234
  ownTopics: [...ownTopics].filter(Boolean).sort(),
120
235
  sqsQueues: [...sqsQueues].filter(Boolean).sort(),
121
- triggerCacheQueues,
122
236
  dsqQueues: [...dsqQueues].filter(Boolean).sort(),
123
237
  beforeLogical,
124
238
  customRoles,
125
239
  websocket,
126
240
  webSocketRoles,
127
241
  webSocketSubscriptions: websocket
128
- ? [...outTopics].filter(topic => (
129
- flows.some(flow => flow?.outputTopic && upperFirst(flow.flowTag) === topic) ||
130
- (relationships.length > 0 && topic !== 'GetRelationship' && RELATIONSHIP_ACTIONS.includes(topic))
131
- )).sort()
242
+ ? [...outTopics]
243
+ .filter(
244
+ topic =>
245
+ flows.some(
246
+ flow => (flow?.outputTopic || (includesEvent(flow, 'ownTopic') && flow.initiateBy === 'user')) && upperFirst(flow.flowTag) === topic
247
+ ) ||
248
+ (relationships.length > 0 &&
249
+ topic !== 'GetRelationship' &&
250
+ RELATIONSHIP_ACTIONS.includes(topic))
251
+ )
252
+ .sort()
132
253
  : [],
133
254
  outTopics: [...outTopics].filter(Boolean).sort(),
134
- tables: [...tableMap.entries()].map(([tableName, keys]) => ({ tableName, ...keys })),
255
+ tables: [...tableMap.entries()].map(([tableName, keys]) => ({
256
+ tableName,
257
+ ...keys
258
+ })),
135
259
  allTableNames: [...new Set([...SYSTEM_TABLES, ...tableMap.keys()])].sort(),
136
260
  objectTableNames,
137
261
  relationshipTableNames,
138
- objectTablesByType: new Map(objects.map(object => [object.objectType, Object.values(object.storageResources ?? {}).find(resource => resource?.storageType === 'dynamoDB')?.tableName]).filter(([, tableName]) => tableName)),
262
+ objectTablesByType: new Map(
263
+ objects
264
+ .map(object => [
265
+ object.objectType,
266
+ Object.values(object.storageResources ?? {}).find(
267
+ resource => resource?.storageType === 'dynamoDB'
268
+ )?.tableName
269
+ ])
270
+ .filter(([, tableName]) => tableName)
271
+ ),
139
272
  flows
140
273
  };
141
274
  }
142
275
 
143
276
  async function renderInSqs(state) {
144
277
  const chunks = await Promise.all([
145
- ...state.ownTopics.map(queueName => render('ResourceYaml.ejs', { queueName })),
146
- ...state.sqsQueues.map(queueName => render('ResourceSqsYaml.ejs', { queueName })),
147
- ...state.triggerCacheQueues.map(queue => render('ResourceSqsYaml.ejs', queue)),
148
- ...state.dsqQueues.map(queueName => render('ResourceSqsYaml.ejs', { queueName })),
149
- ...state.beforeLogical.map(({ action, flowTags }) => render('ResourceBeforeLogicalYaml.ejs', { queueName: `${action}BeforeLogical`, flowTags })),
150
- ...(state.websocket ? [render('ResourceSqsYaml.ejs', { queueName: 'WebSocketCompleteHdrSqs' })] : [])
278
+ ...state.ownTopics.map(queueName =>
279
+ render('ResourceYaml.ejs', { queueName })
280
+ ),
281
+ ...state.sqsQueues.map(queueName =>
282
+ render('ResourceSqsYaml.ejs', { queueName })
283
+ ),
284
+ ...state.dsqQueues.map(queueName =>
285
+ render('ResourceSqsYaml.ejs', { queueName })
286
+ ),
287
+ ...state.beforeLogical.map(({ action, flowTags }) =>
288
+ render('ResourceBeforeLogicalYaml.ejs', {
289
+ queueName: `${action}BeforeLogical`,
290
+ flowTags
291
+ })
292
+ ),
293
+ ...(state.websocket
294
+ ? [
295
+ render('ResourceSqsYaml.ejs', {
296
+ queueName: 'WebSocketCompleteHdrSqs'
297
+ })
298
+ ]
299
+ : [])
151
300
  ]);
152
301
  return `Resources:\n${chunks.join('\n')}\n${state.webSocketSubscriptions.map(renderWebSocketSubscription).join('\n')}`;
153
302
  }
@@ -171,19 +320,46 @@ async function renderIam(state) {
171
320
  if (state.rbac) roles.push('FlowRbacRole');
172
321
  if (state.websocket) roles.push('WebSocketMainRole');
173
322
  roles.push(...state.customRoles);
174
- return render('SharedResource_Yaml.ejs', { roles: roles.sort().map(roleName => ({ roleName, statements: roleStatements(state, roleName) })) });
323
+ return render('SharedResource_Yaml.ejs', {
324
+ roles: roles.sort().map(roleName => ({
325
+ roleName,
326
+ statements: roleStatements(state, roleName)
327
+ }))
328
+ });
175
329
  }
176
330
 
177
331
  function roleStatements(state, roleName) {
178
332
  const statements = [];
179
333
  if (roleNeedsSchemaRead(state, roleName)) {
180
- statements.push({ action: ['s3:GetObject'], resource: ['arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}/perServiceSchemas/*'] });
334
+ statements.push({
335
+ action: ['s3:GetObject'],
336
+ resource: [
337
+ 'arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}/perServiceSchemas/*'
338
+ ]
339
+ });
181
340
  }
182
- addStatement(statements, dynamoActionsForRole(roleName), dynamoArns(roleTableNames(state, roleName)));
183
- addStatement(statements, SQS_CONSUMER_ACTIONS, queueArns(roleQueueNames(state, roleName)));
184
- addStatement(statements, ['sns:Publish'], topicArns(roleTopicNames(state, roleName)));
341
+ addStatement(
342
+ statements,
343
+ dynamoActionsForRole(roleName),
344
+ dynamoArns(roleTableNames(state, roleName))
345
+ );
346
+ addStatement(
347
+ statements,
348
+ SQS_CONSUMER_ACTIONS,
349
+ queueArns(roleQueueNames(state, roleName))
350
+ );
351
+ addStatement(
352
+ statements,
353
+ ['sns:Publish'],
354
+ topicArns(roleTopicNames(state, roleName))
355
+ );
185
356
  if (state.webSocketRoles.has(roleName)) {
186
- statements.push({ action: ['execute-api:ManageConnections'], resource: ['arn:aws:execute-api:${self:custom.iz_region}:${self:custom.iz_accountId}:${self:custom.iz_webSocketHostId}/*'] });
357
+ statements.push({
358
+ action: ['execute-api:ManageConnections'],
359
+ resource: [
360
+ 'arn:aws:execute-api:${self:custom.iz_region}:${self:custom.iz_accountId}:*/*'
361
+ ]
362
+ });
187
363
  }
188
364
  return statements;
189
365
  }
@@ -199,15 +375,39 @@ function roleNeedsSchemaRead(state, roleName) {
199
375
 
200
376
  function roleTableNames(state, roleName) {
201
377
  if (roleName === 'RegisterRole') return [];
202
- if (roleName === 'WebSocketMainRole') return ['UserUploadRecords', 'WebSocketTask'];
203
- if (roleName === 'FlowObjectsRole') return [...state.objectTableNames, 'WebSocketTask'];
204
- if (roleName === 'FlowRelationshipRole') return [...state.objectTableNames, ...state.relationshipTableNames, 'AwaitingMultipleSteps', 'WebSocketTask'];
378
+ if (roleName === 'WebSocketMainRole')
379
+ return ['UserUploadRecords', 'WebSocketTask'];
380
+ if (roleName === 'FlowObjectsRole')
381
+ return [
382
+ ...state.objectTableNames,
383
+ 'AwaitingMultipleSteps',
384
+ 'AwaitingMultipleStepByPending',
385
+ 'WebSocketTask'
386
+ ];
387
+ if (roleName === 'FlowRelationshipRole')
388
+ return [
389
+ ...state.objectTableNames,
390
+ ...state.relationshipTableNames,
391
+ 'AwaitingMultipleSteps',
392
+ 'AwaitingMultipleStepByPending',
393
+ 'WebSocketTask'
394
+ ];
205
395
  if (roleName === 'FlowRbacRole') return RBAC_TABLES;
206
- if (roleName === 'ProcFindDataRole') return [...state.objectTableNames, ...state.relationshipTableNames, 'FindDataMain', 'LogicalResultsMain', 'LogicalResultsData'];
207
- const flow = state.flows.find(candidate => customFlowRoleName(candidate?.flowTag ?? '') === roleName);
396
+ if (roleName === 'ProcFindDataRole')
397
+ return [
398
+ ...state.objectTableNames,
399
+ ...state.relationshipTableNames,
400
+ 'FindDataMain',
401
+ 'LogicalResultsMain',
402
+ 'LogicalResultsData'
403
+ ];
404
+ const flow = state.flows.find(
405
+ candidate => customFlowRoleName(candidate?.flowTag ?? '') === roleName
406
+ );
208
407
  if (!flow) return [];
209
408
  const tables = [];
210
- if (includesEvent(flow, 'ownTopic') || includesEvent(flow, 's3')) tables.push('WebSocketTask');
409
+ if (includesEvent(flow, 'ownTopic') || includesEvent(flow, 's3'))
410
+ tables.push('WebSocketTask');
211
411
  if (includesEvent(flow, 's3')) tables.push('UserUploadRecords');
212
412
  if (flow.statusType === 'storedCache') {
213
413
  const object = state.objectTablesByType.get(flow.objType?.objectType);
@@ -217,56 +417,106 @@ function roleTableNames(state, roleName) {
217
417
  }
218
418
 
219
419
  function dynamoActionsForRole(roleName) {
220
- if (roleName === 'ProcFindDataRole') return ['dynamodb:GetItem', 'dynamodb:Query'];
221
- if (roleName === 'WebSocketMainRole') return ['dynamodb:DeleteItem', 'dynamodb:PutItem', 'dynamodb:Query'];
222
- if (roleName === 'FlowRbacRole') return [...DYNAMO_ACTIONS, 'dynamodb:TransactWriteItems'];
420
+ if (roleName === 'ProcFindDataRole')
421
+ return ['dynamodb:GetItem', 'dynamodb:Query'];
422
+ if (roleName === 'WebSocketMainRole')
423
+ return ['dynamodb:DeleteItem', 'dynamodb:PutItem', 'dynamodb:Query'];
424
+ if (roleName === 'FlowRbacRole')
425
+ return [...DYNAMO_ACTIONS, 'dynamodb:TransactWriteItems'];
223
426
  return DYNAMO_ACTIONS;
224
427
  }
225
428
 
226
429
  function roleQueueNames(state, roleName) {
227
430
  if (roleName === 'ProcFindDataRole') return LEGACY_PROCESS_QUEUES;
228
- if (roleName === 'WebSocketMainRole') return state.websocket ? ['WebSocketCompleteHdrSqs'] : [];
229
- if (roleName === 'FlowObjectsRole') return [
230
- ...CRUD_ACTIONS.map(action => `${action}HdrSqs`),
231
- ...CRUD_ACTIONS.filter(action => action !== 'Get').map(action => `${action}CompleteHdrSqs`),
232
- ...state.beforeLogical.filter(({ action }) => CRUD_ACTIONS.includes(action)).map(({ action }) => `${action}BeforeLogicalHdrSqs`)
233
- ];
234
- if (roleName === 'FlowRelationshipRole') return [
235
- ...RELATIONSHIP_ACTIONS.flatMap(action => [`${action}HdrSqs`, `${action}CompleteHdrSqs`]),
236
- 'GetRelationshipHdrDsq'
237
- ];
238
- if (roleName === 'FlowRbacRole') return RBAC_ACTIONS.flatMap(action => [`${action}HdrSqs`, `Process${action}HdrSqs`]);
239
- const flow = state.flows.find(candidate => customFlowRoleName(candidate?.flowTag ?? '') === roleName);
431
+ if (roleName === 'WebSocketMainRole')
432
+ return state.websocket ? ['WebSocketCompleteHdrSqs'] : [];
433
+ if (roleName === 'FlowObjectsRole')
434
+ return [
435
+ ...CRUD_ACTIONS.map(action => `${action}HdrSqs`),
436
+ ...CRUD_ACTIONS.filter(action => action !== 'Get').map(
437
+ action => `${action}CompleteHdrSqs`
438
+ ),
439
+ ...state.beforeLogical
440
+ .filter(({ action }) => CRUD_ACTIONS.includes(action))
441
+ .map(({ action }) => `${action}BeforeLogicalHdrSqs`)
442
+ ];
443
+ if (roleName === 'FlowRelationshipRole')
444
+ return [
445
+ ...RELATIONSHIP_ACTIONS.flatMap(action => [
446
+ `${action}HdrSqs`,
447
+ `${action}CompleteHdrSqs`
448
+ ]),
449
+ 'GetRelationshipHdrDsq'
450
+ ];
451
+ if (roleName === 'FlowRbacRole')
452
+ return RBAC_ACTIONS.flatMap(action => [
453
+ `${action}HdrSqs`,
454
+ `Process${action}HdrSqs`
455
+ ]);
456
+ const flow = state.flows.find(
457
+ candidate => customFlowRoleName(candidate?.flowTag ?? '') === roleName
458
+ );
240
459
  if (!flow) return [];
241
460
  const tag = upperFirst(flow.flowTag);
242
461
  return [
243
- ...(includesEvent(flow, 'extTopic') ? [`Process${tag}HdrSqs`] : []),
462
+ ...(includesEvent(flow, 'extTopic') ? [`${tag}HdrSqs`] : []),
244
463
  ...(includesEvent(flow, 'ownTopic') ? [`${tag}HdrSqs`] : []),
245
- ...flowExtTopicSteps([flow]).map(({ flowTag, stepName }) => `${flowTag}${stepName}HdrSqs`),
246
- ...state.sqsQueues.filter(queueName => includesEvent(flow, 's3') && queueName.startsWith(tag)),
247
- ...(flow.statusType === 'triggerCache' ? triggerCacheQueueNames(flow) : [])
464
+ ...(includesEvent(flow, 'dsq') ? [`${tag}HdrDsq`] : []),
465
+ ...flowExtTopicSteps([flow]).map(
466
+ ({ flowTag, stepName }) => `${flowTag}${stepName}HdrSqs`
467
+ ),
468
+ ...flowDsqSteps([flow]).map(
469
+ ({ flowTag, stepName }) => `${flowTag}${stepName}HdrDsq`
470
+ ),
471
+ ...state.sqsQueues.filter(
472
+ queueName => includesEvent(flow, 's3') && queueName.startsWith(tag)
473
+ )
248
474
  ];
249
475
  }
250
476
 
251
477
  function roleTopicNames(state, roleName) {
252
478
  if (roleName === 'ProcFindDataRole') return [];
253
- if (roleName === 'FlowObjectsRole') return state.outTopics.filter(topic => CRUD_ACTIONS.includes(topic));
254
- if (roleName === 'FlowRelationshipRole') return state.outTopics.filter(topic => RELATIONSHIP_ACTIONS.includes(topic));
255
- if (roleName === 'FlowRbacRole') return state.outTopics.filter(topic => RBAC_ACTIONS.includes(topic));
256
- const flow = state.flows.find(candidate => customFlowRoleName(candidate?.flowTag ?? '') === roleName);
257
- return flow ? state.outTopics.filter(topic => topic === upperFirst(flow.flowTag)) : [];
479
+ if (roleName === 'FlowObjectsRole')
480
+ return state.outTopics.filter(topic => CRUD_ACTIONS.includes(topic));
481
+ if (roleName === 'FlowRelationshipRole')
482
+ return state.outTopics.filter(topic =>
483
+ RELATIONSHIP_ACTIONS.includes(topic)
484
+ );
485
+ if (roleName === 'FlowRbacRole')
486
+ return state.outTopics.filter(topic => RBAC_ACTIONS.includes(topic));
487
+ const flow = state.flows.find(
488
+ candidate => customFlowRoleName(candidate?.flowTag ?? '') === roleName
489
+ );
490
+ return flow
491
+ ? state.outTopics.filter(topic => topic === upperFirst(flow.flowTag))
492
+ : [];
258
493
  }
259
494
 
260
495
  function dynamoArns(tableNames) {
261
- return [...new Set(tableNames)].sort().map(tableName => `arn:aws:dynamodb:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:table/${'${self:custom.iz_resourcePrefix}'}${tableName}`);
496
+ return [...new Set(tableNames)]
497
+ .sort()
498
+ .map(
499
+ tableName =>
500
+ `arn:aws:dynamodb:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:table/${'${self:custom.iz_resourcePrefix}'}${tableName}`
501
+ );
262
502
  }
263
503
 
264
504
  function queueArns(queueNames) {
265
- return [...new Set(queueNames)].sort().map(queueName => `arn:aws:sqs:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:${'${self:custom.iz_resourcePrefix}'}${queueName}`);
505
+ return [...new Set(queueNames)]
506
+ .sort()
507
+ .map(
508
+ queueName =>
509
+ `arn:aws:sqs:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:${'${self:custom.iz_resourcePrefix}'}${queueName}`
510
+ );
266
511
  }
267
512
 
268
513
  function topicArns(topicNames) {
269
- return [...new Set(topicNames)].sort().map(topic => `arn:aws:sns:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:${'${self:custom.iz_serviceTag}'}_${'${self:custom.iz_stage}'}_${topic}_Out`);
514
+ return [...new Set(topicNames)]
515
+ .sort()
516
+ .map(
517
+ topic =>
518
+ `arn:aws:sns:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:${'${self:custom.iz_serviceTag}'}_${'${self:custom.iz_stage}'}_${topic}_Out`
519
+ );
270
520
  }
271
521
 
272
522
  function renderWebSocketSubscription(topicName) {
@@ -277,11 +527,27 @@ function collectTables(schemas) {
277
527
  const tables = new Map();
278
528
  for (const schema of schemas) {
279
529
  if (!schema?.storageResources) continue;
280
- const identifiers = Array.isArray(schema.identifiers) ? schema.identifiers : [];
281
- const pk = keyName(identifiers.find(identifier => identifier.type === 'partitionKey' || identifier.type === 'identifier')) ?? 'PK';
282
- const sk = keyName(identifiers.find(identifier => identifier.type === 'sortKey'));
530
+ const identifiers = Array.isArray(schema.identifiers)
531
+ ? schema.identifiers
532
+ : [];
533
+ const pk =
534
+ keyName(
535
+ identifiers.find(
536
+ identifier =>
537
+ identifier.type === 'partitionKey' ||
538
+ identifier.type === 'identifier'
539
+ )
540
+ ) ?? 'PK';
541
+ const sk = keyName(
542
+ identifiers.find(identifier => identifier.type === 'sortKey')
543
+ );
283
544
  for (const resource of Object.values(schema.storageResources)) {
284
- if (resource?.storageType === 'dynamoDB' && resource.tableName && !tables.has(resource.tableName)) tables.set(resource.tableName, { pk, sk });
545
+ if (
546
+ resource?.storageType === 'dynamoDB' &&
547
+ resource.tableName &&
548
+ !tables.has(resource.tableName)
549
+ )
550
+ tables.set(resource.tableName, { pk, sk });
285
551
  }
286
552
  }
287
553
  return tables;
@@ -289,12 +555,41 @@ function collectTables(schemas) {
289
555
 
290
556
  function keyName(identifier) {
291
557
  if (!identifier) return undefined;
292
- return identifier.name ?? identifier.fieldName ?? (Array.isArray(identifier.fieldNames) ? identifier.fieldNames.join(identifier.deliminator ?? '_') : undefined);
558
+ return (
559
+ identifier.name ??
560
+ identifier.fieldName ??
561
+ (Array.isArray(identifier.fieldNames)
562
+ ? identifier.fieldNames.join(identifier.deliminator ?? '_')
563
+ : undefined)
564
+ );
293
565
  }
294
566
 
295
- function isCrudObject(object) { return object?.generatedBy === 'userGenerated' || object?.generatedBy === 'systemGenerated'; }
296
- function hasRbacAddOn(object) { return Array.isArray(object?.addOnDataStructure) && object.addOnDataStructure.some(addOn => addOn?.type === 'rbac'); }
297
- function includesEvent(flow, event) { return Array.isArray(flow?.event) && flow.event.includes(event); }
298
- function upperFirst(value) { return typeof value === 'string' && value ? value[0].toUpperCase() + value.slice(1) : ''; }
299
- function normalizeYaml(content) { return `${content.trimEnd()}\n`; }
300
- async function render(templateName, data) { return ejs.render(await readFile(new URL(templateName, TEMPLATE_URL), 'utf8'), { resourceQueueName: data.queueName, ...data }); }
567
+ function isCrudObject(object) {
568
+ return (
569
+ object?.generatedBy === 'userGenerated' ||
570
+ object?.generatedBy === 'systemGenerated'
571
+ );
572
+ }
573
+ function hasRbacAddOn(object) {
574
+ return (
575
+ Array.isArray(object?.addOnDataStructure) &&
576
+ object.addOnDataStructure.some(addOn => addOn?.type === 'rbac')
577
+ );
578
+ }
579
+ function includesEvent(flow, event) {
580
+ return Array.isArray(flow?.event) && flow.event.includes(event);
581
+ }
582
+ function upperFirst(value) {
583
+ return typeof value === 'string' && value
584
+ ? value[0].toUpperCase() + value.slice(1)
585
+ : '';
586
+ }
587
+ function normalizeYaml(content) {
588
+ return `${content.trimEnd()}\n`;
589
+ }
590
+ async function render(templateName, data) {
591
+ return ejs.render(
592
+ await readFile(new URL(templateName, TEMPLATE_URL), 'utf8'),
593
+ { resourceQueueName: data.queueName, ...data }
594
+ );
595
+ }
@@ -25,7 +25,8 @@ export function systemWebSocketManagedPaths() {
25
25
  }
26
26
 
27
27
  export function hasSystemWebSocket(flows, { includeRelationshipOwnTopic = false } = {}) {
28
- return includeRelationshipOwnTopic || (Array.isArray(flows) && flows.some(flow => Array.isArray(flow?.event) && flow.event.includes('ownTopic')));
28
+ // return true;
29
+ return Array.isArray(flows) && flows.some(flow => Array.isArray(flow?.event) && (flow.event.includes('s3') || (includeRelationshipOwnTopic && flow.event.includes('ownTopic') && flow.initiateBy === 'user')));
29
30
  }
30
31
 
31
32
  function renderWebSocketConnectYaml() {