@izara_project/izara-core-generate-service-code 1.0.98 → 1.0.100

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 (70) hide show
  1. package/package.json +1 -1
  2. package/src/compile/compileProject.js +16 -5
  3. package/src/compile/flowValidator.js +47 -8
  4. package/src/compile/loadSchemas.js +18 -7
  5. package/src/compile/schemaValidatorSetup.js +16 -0
  6. package/src/emit/application/coreFunction/emitCoreFunctionArtifacts.js +123 -109
  7. package/src/emit/application/coreFunction/templates/InitialSetupConfig.ejs +8 -4
  8. package/src/emit/application/coreFunction/templates/InitialSetupRunner.ejs +2 -2
  9. package/src/emit/application/crud/templates/CreateComplete_Main.ejs +2 -0
  10. package/src/emit/application/crud/templates/CreateEndpoint_Main.ejs +4 -4
  11. package/src/emit/application/crud/templates/CreateLogicalLib.ejs +74 -15
  12. package/src/emit/application/crud/templates/DeleteComplete_Main.ejs +2 -0
  13. package/src/emit/application/crud/templates/DeleteEndpoint_Main.ejs +4 -4
  14. package/src/emit/application/crud/templates/FlowEndpoint_HdrWbs.ejs +7 -5
  15. package/src/emit/application/crud/templates/UpdateComplete_Main.ejs +2 -0
  16. package/src/emit/application/crud/templates/UpdateEndpoint_Main.ejs +4 -4
  17. package/src/emit/application/custom/customFlowPaths.js +13 -16
  18. package/src/emit/application/custom/customFlowS3.js +277 -66
  19. package/src/emit/application/custom/customFlows.js +131 -0
  20. package/src/emit/application/custom/emitCustomFlowApplicationArtifacts.js +3 -1
  21. package/src/emit/application/custom/templates/FlowEndpoint_Main.ejs +2 -2
  22. package/src/emit/application/custom/templates/s3/ConfirmAfterUpload_HdrSqs.ejs +110 -0
  23. package/src/emit/application/custom/templates/s3/ConfirmAfterUpload_Main.ejs +89 -0
  24. package/src/emit/application/custom/templates/s3/ConfirmComplete_HdrSqs.ejs +110 -0
  25. package/src/emit/application/custom/templates/s3/ConfirmComplete_Main.ejs +89 -0
  26. package/src/emit/application/custom/templates/s3/CreateObject_HdrWbs.ejs +2 -1
  27. package/src/emit/application/custom/templates/s3/CreateObject_Main.ejs +4 -0
  28. package/src/emit/application/custom/templates/s3/CreatePreSignUrl_HdrSqs.ejs +42 -0
  29. package/src/emit/application/custom/templates/s3/CreatePreSignUrl_Main.ejs +23 -0
  30. package/src/emit/application/custom/templates/s3/ProcessS3File_Main.ejs +135 -27
  31. package/src/emit/application/custom/templates/s3/Start_Main.ejs +197 -0
  32. package/src/emit/application/rbac/emitDynamicRbacFlowSchemas.js +8 -1
  33. package/src/emit/application/rbac/emitRbacApplicationArtifacts.js +107 -25
  34. package/src/emit/application/rbac/templates/ReservedDynamicUsage_Main.ejs +29 -9
  35. package/src/emit/application/rbac/templates/ReservedUsageTtlExpired_HdrDss.ejs +176 -0
  36. package/src/emit/application/rbac/templates/UserRbacFlow_Main.ejs +4 -3
  37. package/src/emit/application/system/templates/Register_HdrWbs.ejs +2 -1
  38. package/src/emit/infrastructure/functions/functionYamlArtifact.js +31 -8
  39. package/src/emit/infrastructure/functions/templates/DssFunction_Yaml.ejs +21 -0
  40. package/src/emit/infrastructure/functions/templates/S3Function_Yaml.ejs +2 -3
  41. package/src/emit/infrastructure/resources/iamResourceArtifacts.js +67 -5
  42. package/src/emit/infrastructure/resources/resourceState.js +15 -5
  43. package/src/emit/infrastructure/resources/s3ResourceArtifacts.js +19 -3
  44. package/src/emit/infrastructure/resources/templates/GeneratedLambdaRoleServiceConfig.ejs +5 -3
  45. package/src/emit/infrastructure/resources/templates/S3Bucket_Yaml.ejs +1 -1
  46. package/src/emit/infrastructure/resources/templates/SystemDynamoDB_Yaml.ejs +2 -2
  47. package/src/emit/schemas/attributeTree/emitAttributeTreeSchemas.js +62 -1
  48. package/src/emit/schemas/error/emitErrorSchemas.js +120 -10
  49. package/src/emit/schemas/error/templates/errorConsts.ejs +12 -0
  50. package/src/generate.js +32 -34
  51. package/src/output/mergeHooks.js +105 -5
  52. package/src/plan/domainRegistry.js +0 -10
  53. package/task.md +40 -96
  54. package/test/integration/graphDrivenFlowSchema.test.js +58 -0
  55. package/test/integration/publicApi.test.js +33 -24
  56. package/test/unit/application/createTargetRoleHandler.test.js +101 -9
  57. package/test/unit/application/crudApplicationArtifacts.test.js +4 -0
  58. package/test/unit/application/customFlowApplicationArtifacts.test.js +6 -6
  59. package/test/unit/application/customFlowS3.test.js +730 -0
  60. package/test/unit/infrastructure/functionYamlArtifact.test.js +6 -4
  61. package/test/unit/infrastructure/resourceArtifacts.test.js +103 -0
  62. package/test/unit/output/mergeHooks.test.js +137 -0
  63. package/test/unit/schemas/attributeTreeSchemas.test.js +32 -3
  64. package/test/unit/schemas/attributeTreeSingleService.test.js +175 -0
  65. package/test/unit/schemas/dynamicRbacFlowSchemas.test.js +8 -6
  66. package/test/unit/schemas/dynamicRelationshipFlowSchemas.test.js +51 -0
  67. package/test/unit/schemas/errorSchemas.test.js +84 -5
  68. package/test/unit/templates/hookTagsValidation.test.js +83 -0
  69. package/src/emit/schemas/attributeTree/emitAttributeTreeTargetValueStandard.js +0 -132
  70. package/test/unit/schemas/attributeTreeDirectives.test.js +0 -177
@@ -181,6 +181,7 @@ export function renderS3FunctionYaml({
181
181
  handlerPath,
182
182
  roleName,
183
183
  bucketName,
184
+ s3KeyPrefix,
184
185
  flowTag,
185
186
  flowStepName
186
187
  }) {
@@ -189,6 +190,35 @@ export function renderS3FunctionYaml({
189
190
  handlerPath,
190
191
  roleName,
191
192
  bucketName,
193
+ s3KeyPrefix,
194
+ environmentSetting: _environmentSetting(
195
+ functionName,
196
+ flowTag,
197
+ flowStepName
198
+ ),
199
+ functionSetting: _functionSetting(functionName)
200
+ });
201
+ }
202
+
203
+ export function renderDssFunctionYaml({
204
+ functionName,
205
+ handlerPath,
206
+ roleName,
207
+ tableName,
208
+ batchSize = 100,
209
+ startingPosition = 'TRIM_HORIZON',
210
+ filterPatterns = [],
211
+ flowTag,
212
+ flowStepName
213
+ }) {
214
+ return _renderFunctionTemplate('DssFunction_Yaml.ejs', {
215
+ functionName,
216
+ handlerPath,
217
+ roleName,
218
+ tableName,
219
+ batchSize,
220
+ startingPosition,
221
+ filterPatterns,
192
222
  environmentSetting: _environmentSetting(
193
223
  functionName,
194
224
  flowTag,
@@ -314,15 +344,8 @@ function _sqsFilterPatterns(
314
344
  - body:
315
345
  MessageAttributes:
316
346
  callingFlow:
317
- Value:
347
+ StringValue:
318
348
  - \${self:custom.iz_resourcePrefix}${callingFlowName}
319
- - body:
320
- MessageAttributes:
321
- callingFlow:
322
- - exists: false
323
- - body:
324
- MessageAttributes:
325
- - exists: false
326
349
  #<#/${hookFunctionName}FilterPatterns#>
327
350
  `;
328
351
  }
@@ -0,0 +1,21 @@
1
+ <%= functionName %>:
2
+ handler: <%= handlerPath %>
3
+ name: ${self:custom.iz_resourcePrefix}<%= functionName %>
4
+ role: <%= roleName %>
5
+ <%- environmentSetting %><%- functionSetting %>
6
+ events:
7
+ - stream:
8
+ type: dynamodb
9
+ arn:
10
+ Fn::GetAtt: [<%= tableName %>Table, StreamArn]
11
+ batchSize: <%= batchSize %>
12
+ startingPosition: <%= startingPosition %>
13
+ <% if (Array.isArray(filterPatterns) && filterPatterns.length > 0) { -%>
14
+ filterPatterns:
15
+ <% filterPatterns.forEach(function(pattern) { -%>
16
+ - eventName: <%- JSON.stringify(pattern.eventName) %>
17
+ userIdentity:
18
+ type: <%- JSON.stringify(pattern.userIdentity.type) %>
19
+ principalId: <%- JSON.stringify(pattern.userIdentity.principalId) %>
20
+ <% }); -%>
21
+ <% } -%>
@@ -8,9 +8,8 @@
8
8
  bucket: <%= bucketName %>
9
9
  event: s3:ObjectCreated:*
10
10
  existing: true
11
- rules:
11
+ rules:<% if (s3KeyPrefix) { %>
12
+ - prefix: <%- s3KeyPrefix %><% } %>
12
13
  #<#<%= functionName %>rules#>
13
14
  #<#/<%= functionName %>rules#>
14
- - prefix: uploads/
15
- - suffix: .csv
16
15
  forceDeploy: true
@@ -96,9 +96,26 @@ export function getRbacValidatorServiceTags(state) {
96
96
  export async function renderLambdaRoleConfig(state) {
97
97
  const s3Flows = state.flows
98
98
  .filter((flow) => includesEvent(flow, 's3'))
99
- .map((flow) => ({
100
- roleName: upperFirst(flow.flowTag)
101
- }));
99
+ .map((flow) => {
100
+ let quotaServiceTag = flow.quotaServiceTag;
101
+ if (!quotaServiceTag && flow.belongTo) {
102
+ if (Array.isArray(flow.belongTo)) {
103
+ quotaServiceTag = flow.belongTo[0]?.serviceTag;
104
+ } else if (flow.belongTo.serviceTag) {
105
+ quotaServiceTag = flow.belongTo.serviceTag;
106
+ }
107
+ }
108
+ if (!quotaServiceTag && flow.objType?.serviceTag) {
109
+ quotaServiceTag = flow.objType.serviceTag;
110
+ }
111
+ if (!quotaServiceTag) {
112
+ quotaServiceTag = state.project?.serviceTag || 'Rbac';
113
+ }
114
+ return {
115
+ roleName: upperFirst(flow.flowTag),
116
+ quotaServiceTag
117
+ };
118
+ });
102
119
  const rbacValidatorServiceTags = getRbacValidatorServiceTags(state);
103
120
  return renderResourceTemplate('GeneratedLambdaRoleServiceConfig.ejs', {
104
121
  crud: state.crud,
@@ -122,6 +139,25 @@ export function roleStatements(state, roleName) {
122
139
  resource: ['arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}']
123
140
  });
124
141
  }
142
+ const s3FlowsForRole = (state.flows || []).filter((candidate) => {
143
+ const roleCandidate = customFlowRoleName(candidate?.flowTag ?? '');
144
+ return (
145
+ roleCandidate === roleName &&
146
+ includesEvent(candidate, 's3') &&
147
+ Boolean(candidate.bucketName)
148
+ );
149
+ });
150
+ if (s3FlowsForRole.length > 0) {
151
+ const bucketResources = [
152
+ ...new Set(
153
+ s3FlowsForRole.map((flow) => `arn:aws:s3:::${flow.bucketName}/*`)
154
+ )
155
+ ].sort();
156
+ statements.push({
157
+ action: ['s3:GetObject'],
158
+ resource: bucketResources
159
+ });
160
+ }
125
161
  addStatement(statements, dynamoActionsForRole(roleName), [
126
162
  ...dynamoArns(roleTableNames(state, roleName)),
127
163
  ...externalDynamoArns(roleExternalDynamoTables(state, roleName))
@@ -160,6 +196,19 @@ export function roleStatements(state, roleName) {
160
196
  ]
161
197
  });
162
198
  }
199
+ if (roleName === 'FlowRbacRole' && state.selfRbac) {
200
+ statements.push({
201
+ action: [
202
+ 'dynamodb:DescribeStream',
203
+ 'dynamodb:GetRecords',
204
+ 'dynamodb:GetShardIterator',
205
+ 'dynamodb:ListStreams'
206
+ ],
207
+ resource: [
208
+ 'arn:aws:dynamodb:${self:custom.iz_region}:${self:custom.iz_accountId}:table/${self:custom.iz_resourcePrefix}RunningTotalReservedUsage/stream/*'
209
+ ]
210
+ });
211
+ }
163
212
  return statements;
164
213
  }
165
214
 
@@ -449,6 +498,9 @@ export function roleTopicNames(state, roleName) {
449
498
  if (flowHasOwnTopic(flow)) {
450
499
  topics.push(`${name}_In`);
451
500
  }
501
+ if (includesEvent(flow, 's3')) {
502
+ topics.push('Create_In');
503
+ }
452
504
  return topics;
453
505
  }
454
506
  return [];
@@ -508,11 +560,16 @@ export function roleExternalTopicArns(state, roleName) {
508
560
  `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:${tag}_\${self:custom.iz_stage}_ValidatorRbac_In`
509
561
  );
510
562
  if (roleName === 'FlowObjectsRole') {
563
+ const extraCrudTopics = extraTags.flatMap((tag) => [
564
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:${tag}_\${self:custom.iz_stage}_ValidatorRbac_In`,
565
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:${tag}_\${self:custom.iz_stage}_ConfirmUsage_In`,
566
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:${tag}_\${self:custom.iz_stage}_CancelUsage_In`
567
+ ]);
511
568
  return [
512
569
  `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ValidatorRbac_In`,
513
570
  `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ConfirmUsage_In`,
514
571
  `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_CancelUsage_In`,
515
- ...extraTopics
572
+ ...extraCrudTopics
516
573
  ];
517
574
  }
518
575
  if (roleName === 'FlowRbacRole' || roleName === 'FlowRelationshipRole') {
@@ -536,11 +593,16 @@ export function roleExternalLambdaArns(state, roleName) {
536
593
  `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:${tag}\${self:custom.iz_stage}ValidatorRbacHdrInv`
537
594
  );
538
595
  if (roleName === 'FlowObjectsRole') {
596
+ const extraCrudLambdas = extraTags.flatMap((tag) => [
597
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:${tag}\${self:custom.iz_stage}ValidatorRbacHdrInv`,
598
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:${tag}\${self:custom.iz_stage}ConfirmUsageHdrInv`,
599
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:${tag}\${self:custom.iz_stage}CancelUsageHdrInv`
600
+ ]);
539
601
  return [
540
602
  `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}ValidatorRbacHdrInv`,
541
603
  `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}ConfirmUsageHdrInv`,
542
604
  `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}CancelUsageHdrInv`,
543
- ...extraLambdas
605
+ ...extraCrudLambdas
544
606
  ];
545
607
  }
546
608
  if (roleName === 'FlowRbacRole' || roleName === 'FlowRelationshipRole') {
@@ -4,7 +4,8 @@ import {
4
4
  flowExtTopicSteps,
5
5
  generatedDsqFlows,
6
6
  generatedOwnTopicFlows,
7
- supportedCustomFlows
7
+ supportedCustomFlows,
8
+ resolveS3Parts
8
9
  } from '../../application/custom/customFlows.js';
9
10
  import { hasSystemWebSocket } from '../../application/system/emitSystemWebSocketArtifacts.js';
10
11
  import { rbacFlowTag } from '../../application/rbac/rbacFlowTag.js';
@@ -161,9 +162,12 @@ export function createResourceState({ objects, relationships, flows }) {
161
162
 
162
163
  for (const flow of flows.filter((flow) => includesEvent(flow, 's3'))) {
163
164
  const tag = upperFirst(flow.flowTag);
164
- customSqsQueues.push(`${tag}CreatePreSignUrlHdrSqs`);
165
- customSqsQueues.push(`${tag}ReservedLimitHdrSqs`);
166
- customSqsQueues.push(`${tag}ConfirmAfterUploadHdrSqs`);
165
+ const parts = resolveS3Parts(flow);
166
+ for (const part of parts) {
167
+ if (part.type === 'sqs') {
168
+ customSqsQueues.push(`${tag}${part.action}HdrSqs`);
169
+ }
170
+ }
167
171
  }
168
172
 
169
173
  const customDsqQueues = flows
@@ -281,8 +285,14 @@ export function createResourceState({ objects, relationships, flows }) {
281
285
  topicName = `Out${upperFirst(flow.outputTopic)}`;
282
286
  }
283
287
  }
288
+ let targetAction = 'ReservedLimit';
289
+ const parts = resolveS3Parts(flow);
290
+ const firstSqsPart = parts.find((p) => p.type === 'sqs');
291
+ if (firstSqsPart && firstSqsPart.action) {
292
+ targetAction = firstSqsPart.action;
293
+ }
284
294
  const queueName =
285
- flow.s3Subscription?.queueName || `${tag}ReservedLimitHdrSqs`;
295
+ flow.s3Subscription?.queueName || `${tag}${targetAction}HdrSqs`;
286
296
  const subscriptionName =
287
297
  flow.s3Subscription?.subscriptionName ||
288
298
  `Subscription${topicName}To${queueName}`;
@@ -1,13 +1,29 @@
1
1
  import { indentYaml, renderResourceTemplate } from './resourceTemplate.js';
2
2
 
3
+ export function toAlphanumericPascalCase(str) {
4
+ if (!str) {
5
+ return '';
6
+ }
7
+ const parts = str.split(/[^a-zA-Z0-9]+/);
8
+ const validParts = parts.filter(Boolean);
9
+ const capitalizedParts = validParts.map((word) => {
10
+ return word.charAt(0).toUpperCase() + word.slice(1);
11
+ });
12
+ return capitalizedParts.join('');
13
+ }
14
+
3
15
  export async function renderS3(state) {
4
16
  if (state.s3Buckets.length === 0) {
5
17
  return '';
6
18
  }
7
19
  const renderedBuckets = await Promise.all(
8
- state.s3Buckets.map((bucketName) =>
9
- renderResourceTemplate('S3Bucket_Yaml.ejs', { bucketName })
10
- )
20
+ state.s3Buckets.map((bucketName) => {
21
+ const bucketLogicalId = `${toAlphanumericPascalCase(bucketName)}S3Bucket`;
22
+ return renderResourceTemplate('S3Bucket_Yaml.ejs', {
23
+ bucketName,
24
+ bucketLogicalId
25
+ });
26
+ })
11
27
  );
12
28
  const body = renderedBuckets.join('\n');
13
29
  return `Resources:\n${indentYaml(body, 2)}`;
@@ -60,7 +60,9 @@ export function generatedLambdaRole() {
60
60
  `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ConfirmUsage_In', 'Rbac')}`,
61
61
  `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'CancelUsage_In', 'Rbac')}`<% if (rbac) { %>,
62
62
  `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ValidatorRbac_In')}`<% } %><% (rbacValidatorServiceTags || []).forEach(function(tag) { %>,
63
- `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ValidatorRbac_In', '<%= tag %>')}`<% }); %>
63
+ `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ValidatorRbac_In', '<%= tag %>')}`,
64
+ `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ConfirmUsage_In', '<%= tag %>')}`,
65
+ `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'CancelUsage_In', '<%= tag %>')}`<% }); %>
64
66
  ]
65
67
  }<% } %>
66
68
  //(<createFlowObjectsRole>)
@@ -144,7 +146,7 @@ export function generatedLambdaRole() {
144
146
  Effect: 'Allow',
145
147
  Action: ['sns:Publish'],
146
148
  Resource: [
147
- `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ReservedDynamicUsage_In', 'AccountLimits')}`
149
+ `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ReservedDynamicUsage_In', '<%= flow.quotaServiceTag || 'Rbac' %>')}`
148
150
  ]
149
151
  },
150
152
 
@@ -153,7 +155,7 @@ export function generatedLambdaRole() {
153
155
  Effect: 'Allow',
154
156
  Action: ['sns:Publish'],
155
157
  Resource: [
156
- `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ConfirmDynamicAfterUpload_In', 'AccountLimits')}`
158
+ `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ConfirmDynamicAfterUpload_In', '<%= flow.quotaServiceTag || 'Rbac' %>')}`
157
159
  ]
158
160
  }
159
161
  //(<create<%= flow.roleName %>Role>)
@@ -1,5 +1,5 @@
1
1
  ##===== [S3 Bucket: <%- bucketName %>]
2
- <%- bucketName %>S3Bucket:
2
+ <%- bucketLogicalId %>:
3
3
  Type: AWS::S3::Bucket
4
4
  Properties:
5
5
  BucketName: <%- bucketName %>
@@ -230,7 +230,7 @@ Resources:
230
230
  - AttributeName: cacheKey
231
231
  KeyType: RANGE
232
232
  TimeToLiveSpecification:
233
- AttributeName: ttlTimestamp
233
+ AttributeName: expirationTime
234
234
  Enabled: true
235
235
 
236
236
  RunningTotalUsageTable:
@@ -265,7 +265,7 @@ Resources:
265
265
  - AttributeName: usedTimestampId
266
266
  KeyType: RANGE
267
267
  TimeToLiveSpecification:
268
- AttributeName: ttlTimestamp
268
+ AttributeName: expirationTime
269
269
  Enabled: true
270
270
  StreamSpecification:
271
271
  StreamViewType: NEW_AND_OLD_IMAGES
@@ -62,6 +62,60 @@ function _renderRelationshipSchema(relationshipTag, content) {
62
62
  return `export default ${JSON.stringify(wrapper, null, 2)};\n`;
63
63
  }
64
64
 
65
+ function _renderValueStandardSchema(tag, storageResources) {
66
+ const storageResourceTags = Object.keys(storageResources);
67
+ if (storageResourceTags.length === 0) {
68
+ storageResourceTags.push('myGraph');
69
+ }
70
+
71
+ const storageJson = JSON.stringify(storageResources, null, 2)
72
+ .replace(/\n/g, '\n ');
73
+ const tagsJson = JSON.stringify(storageResourceTags);
74
+
75
+ return `export default [
76
+ {
77
+ objectType: '${tag}ValueStandard',
78
+ canDelete: true,
79
+ generatedBy: 'user',
80
+ storageResources: ${storageJson},
81
+ addOnDataStructure: [
82
+ //(<addOnDataStructure${tag}ValueStandard>)
83
+ // {
84
+ // type: 'propertyValue',
85
+ // propertyValuePrefix: '${tag}ValueStandard'
86
+ // }
87
+ //(</addOnDataStructure${tag}ValueStandard>)
88
+ ],
89
+ fieldNames: {
90
+ ${tag}ValueStandardId: {
91
+ type: 'string',
92
+ randomOnCreate: true,
93
+ canUpdate: false,
94
+ storageResourceTags: ${tagsJson}
95
+ },
96
+ label: {
97
+ type: 'string',
98
+ canUpdate: true,
99
+ storageResourceTags: ${tagsJson}
100
+ },
101
+ value: {
102
+ type: 'string',
103
+ requiredOnCreate: true,
104
+ canUpdate: true,
105
+ storageResourceTags: ${tagsJson}
106
+ }
107
+ },
108
+ identifiers: [
109
+ {
110
+ type: 'identifier',
111
+ fieldName: '${tag}ValueStandardId'
112
+ }
113
+ ]
114
+ }
115
+ ];
116
+ `;
117
+ }
118
+
65
119
  export function attributeTreeObjectSchemaManagedPaths(objects = []) {
66
120
  const trees = _extractAttributeTrees(objects);
67
121
  const paths = [];
@@ -71,7 +125,8 @@ export function attributeTreeObjectSchemaManagedPaths(objects = []) {
71
125
  paths.push(
72
126
  `app/src/schemas/ObjectSchemas/generatedCode/attributeTree/${tag}Link.js`,
73
127
  `app/src/schemas/ObjectSchemas/generatedCode/attributeTree/${tag}Value.js`,
74
- `app/src/schemas/ObjectSchemas/generatedCode/attributeTree/${tag}Label.js`
128
+ `app/src/schemas/ObjectSchemas/generatedCode/attributeTree/${tag}Label.js`,
129
+ `app/src/schemas/ObjectSchemas/generatedCode/attributeTree/${tag}ValueStandard.js`
75
130
  );
76
131
  }
77
132
 
@@ -205,6 +260,12 @@ export function emitAttributeTreeObjectSchemas({ objects = [] } = {}) {
205
260
  path: `app/src/schemas/ObjectSchemas/generatedCode/attributeTree/${tag}Label.js`,
206
261
  content: _renderObjectSchema(labelSchema)
207
262
  });
263
+
264
+ // 4. {tag}ValueStandard.js
265
+ artifacts.push({
266
+ path: `app/src/schemas/ObjectSchemas/generatedCode/attributeTree/${tag}ValueStandard.js`,
267
+ content: _renderValueStandardSchema(tag, graphStorageResources)
268
+ });
208
269
  }
209
270
 
210
271
  return artifacts;
@@ -1,19 +1,43 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
1
2
  import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
2
4
  import ejsLib from 'ejs';
3
5
 
4
6
  const TEMPLATE_URL = new URL('./templates/', import.meta.url);
5
7
  export const ERROR_SCHEMA_PATH =
6
- 'app/src/schemas/ErrorSchemas/generateCode/errorSchema.js';
8
+ 'app/src/schemas/ErrorSchemas/generateCode/errorConsts.js';
9
+ export const ERROR_CONSTS_PATH = ERROR_SCHEMA_PATH;
10
+
11
+ export const SHARED_ERROR_PACKAGE_PATH = 'libs/errorLib/package.json';
12
+ export const SHARED_ERROR_INDEX_PATH = 'libs/errorLib/index.js';
13
+ export const SHARED_ERROR_CONSTS_PATH = 'libs/errorLib/src/errorConsts.js';
7
14
 
8
15
  export function errorSchemaManagedPaths(errors = []) {
9
16
  if (!errors || errors.length === 0) {
10
17
  return [];
11
18
  }
12
- return [ERROR_SCHEMA_PATH];
19
+ const paths = [ERROR_SCHEMA_PATH];
20
+ const safeErrors = Array.isArray(errors) ? errors : [];
21
+ let hasShared = false;
22
+ for (const item of safeErrors) {
23
+ if (item && item.sharedLibrary === true) {
24
+ hasShared = true;
25
+ break;
26
+ }
27
+ }
28
+ if (hasShared) {
29
+ paths.push(
30
+ SHARED_ERROR_PACKAGE_PATH,
31
+ SHARED_ERROR_INDEX_PATH,
32
+ SHARED_ERROR_CONSTS_PATH
33
+ );
34
+ }
35
+ return paths;
13
36
  }
14
37
 
15
38
  /**
16
- * Emits errorSchema.js artifact based on loaded ErrorSchemas
39
+ * Emits errorConsts.js artifact and shared library artifacts based on
40
+ * loaded ErrorSchemas
17
41
  *
18
42
  * @param {Object} params
19
43
  * @param {Object} params.project
@@ -31,21 +55,28 @@ export async function emitErrorSchemas({
31
55
  return [];
32
56
  }
33
57
 
34
- const defaultGraphServiceTag =
35
- objectGraphServiceTags[0] || 'GraphHandler';
36
58
  const defaultServiceTag = project?.serviceTag || '';
37
59
 
38
60
  const errorTypes = {};
61
+ const sharedErrorTypes = {};
62
+
39
63
  for (const item of safeErrors) {
40
64
  if (!item || typeof item !== 'object' || !item.errorTag) {
41
65
  continue;
42
66
  }
43
67
  const errorTag = item.errorTag;
44
- const serviceTag = item.serviceTag || defaultServiceTag;
45
- errorTypes[errorTag] = {
68
+ let serviceTag = item.serviceTag;
69
+ if (!serviceTag) {
70
+ serviceTag = defaultServiceTag;
71
+ }
72
+ const errorEntry = {
46
73
  errorTag,
47
74
  serviceTag
48
75
  };
76
+ errorTypes[errorTag] = errorEntry;
77
+ if (item.sharedLibrary === true) {
78
+ sharedErrorTypes[errorTag] = errorEntry;
79
+ }
49
80
  }
50
81
 
51
82
  const sortedKeys = Object.keys(errorTypes).sort();
@@ -58,16 +89,95 @@ export async function emitErrorSchemas({
58
89
  sortedErrorTypes[key] = errorTypes[key];
59
90
  }
60
91
 
61
- const templatePath = new URL('errorSchema.ejs', TEMPLATE_URL);
92
+ const templatePath = new URL('errorConsts.ejs', TEMPLATE_URL);
62
93
  const template = await readFile(templatePath, 'utf8');
63
94
  const content = ejsLib.render(template, {
64
- errorTypes: sortedErrorTypes
95
+ errorTypes: sortedErrorTypes,
96
+ headerComment: null
65
97
  });
66
98
 
67
- return [
99
+ const artifacts = [
68
100
  {
69
101
  path: ERROR_SCHEMA_PATH,
70
102
  content
71
103
  }
72
104
  ];
105
+
106
+ const sortedSharedKeys = Object.keys(sharedErrorTypes).sort();
107
+ if (sortedSharedKeys.length > 0) {
108
+ const sortedSharedErrorTypes = {};
109
+ for (const key of sortedSharedKeys) {
110
+ sortedSharedErrorTypes[key] = sharedErrorTypes[key];
111
+ }
112
+
113
+ const sharedContent = ejsLib.render(template, {
114
+ errorTypes: sortedSharedErrorTypes,
115
+ headerComment: '// only generate error that sharedLibrary: true'
116
+ });
117
+
118
+ let serviceName = 'service';
119
+ if (project?.serviceTag) {
120
+ serviceName = project.serviceTag.toLowerCase();
121
+ }
122
+ const packageName = `@izara_project/izara-${serviceName}-library-errorlib`;
123
+
124
+ let packageJson = {
125
+ name: packageName,
126
+ version: '1.0.0',
127
+ description: '',
128
+ type: 'module',
129
+ main: 'index.js',
130
+ scripts: {},
131
+ keywords: [],
132
+ author: '',
133
+ license: 'ISC',
134
+ devDependencies: {},
135
+ dependencies: {}
136
+ };
137
+
138
+ let targetDir = project?.outputPath;
139
+ if (!targetDir) {
140
+ targetDir = project?.rootPath;
141
+ }
142
+ if (targetDir) {
143
+ const existingPkgPath = join(targetDir, SHARED_ERROR_PACKAGE_PATH);
144
+ if (existsSync(existingPkgPath)) {
145
+ try {
146
+ const raw = readFileSync(existingPkgPath, 'utf8');
147
+ const parsed = JSON.parse(raw);
148
+ if (parsed && typeof parsed === 'object') {
149
+ packageJson = {
150
+ ...parsed,
151
+ name: packageName,
152
+ type: 'module',
153
+ main: 'index.js'
154
+ };
155
+ }
156
+ } catch {
157
+ // If parsing fails (e.g. comments in JSON), use default packageJson
158
+ }
159
+ }
160
+ }
161
+
162
+ const packageJsonContent = `${JSON.stringify(packageJson, null, 2)}\n`;
163
+ const indexContent =
164
+ "import * as errorLib from './src/errorConsts.js';\n\nexport { errorLib };\n";
165
+
166
+ artifacts.push(
167
+ {
168
+ path: SHARED_ERROR_PACKAGE_PATH,
169
+ content: packageJsonContent
170
+ },
171
+ {
172
+ path: SHARED_ERROR_INDEX_PATH,
173
+ content: indexContent
174
+ },
175
+ {
176
+ path: SHARED_ERROR_CONSTS_PATH,
177
+ content: sharedContent
178
+ }
179
+ );
180
+ }
181
+
182
+ return artifacts;
73
183
  }
@@ -0,0 +1,12 @@
1
+ <% if (locals.headerComment) { -%>
2
+ <%- headerComment %>
3
+ <% } -%>
4
+ export const ERROR_TYPES = {
5
+ <% const keys = Object.keys(errorTypes); -%>
6
+ <% keys.forEach((key, index) => { -%>
7
+ <%- key %>: {
8
+ errorTag: '<%- errorTypes[key].errorTag %>',
9
+ serviceTag: '<%- errorTypes[key].serviceTag %>'
10
+ }<%- index < keys.length - 1 ? ',' : '' %>
11
+ <% }); -%>
12
+ };