@izara_project/izara-core-generate-service-code 1.0.99 → 1.0.101

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 (51) hide show
  1. package/package.json +1 -1
  2. package/src/compile/compileProject.js +16 -5
  3. package/src/compile/flowValidator.js +3 -1
  4. package/src/compile/loadSchemas.js +18 -4
  5. package/src/compile/schemaValidatorSetup.js +16 -0
  6. package/src/emit/application/coreFunction/emitCoreFunctionArtifacts.js +108 -108
  7. package/src/emit/application/coreFunction/templates/InitialSetupRunner.ejs +2 -2
  8. package/src/emit/application/crud/templates/CreateComplete_Main.ejs +2 -0
  9. package/src/emit/application/crud/templates/CreateLogicalLib.ejs +20 -7
  10. package/src/emit/application/crud/templates/DeleteComplete_Main.ejs +2 -0
  11. package/src/emit/application/crud/templates/FlowEndpoint_HdrWbs.ejs +7 -5
  12. package/src/emit/application/crud/templates/UpdateComplete_Main.ejs +2 -0
  13. package/src/emit/application/custom/customFlowPaths.js +13 -16
  14. package/src/emit/application/custom/customFlowS3.js +456 -63
  15. package/src/emit/application/custom/customFlows.js +131 -0
  16. package/src/emit/application/custom/emitCustomFlowApplicationArtifacts.js +7 -4
  17. package/src/emit/application/custom/templates/s3/ConfirmAfterUpload_HdrSqs.ejs +111 -0
  18. package/src/emit/application/custom/templates/s3/ConfirmAfterUpload_Main.ejs +89 -0
  19. package/src/emit/application/custom/templates/s3/ConfirmComplete_HdrSqs.ejs +111 -0
  20. package/src/emit/application/custom/templates/s3/ConfirmComplete_Main.ejs +100 -0
  21. package/src/emit/application/custom/templates/s3/CreateObject_HdrWbs.ejs +2 -1
  22. package/src/emit/application/custom/templates/s3/CreateObject_Main.ejs +4 -0
  23. package/src/emit/application/custom/templates/s3/CreatePreSignUrl_HdrSqs.ejs +42 -0
  24. package/src/emit/application/custom/templates/s3/CreatePreSignUrl_Main.ejs +23 -0
  25. package/src/emit/application/custom/templates/s3/ProcessS3File_Main.ejs +240 -27
  26. package/src/emit/application/custom/templates/s3/Start_Main.ejs +216 -0
  27. package/src/emit/application/rbac/emitDynamicRbacFlowSchemas.js +5 -1
  28. package/src/emit/application/rbac/emitRbacApplicationArtifacts.js +67 -60
  29. package/src/emit/application/rbac/templates/ReservedDynamicUsage_Main.ejs +27 -9
  30. package/src/emit/application/rbac/templates/UserRbacFlow_Main.ejs +4 -3
  31. package/src/emit/application/system/templates/Register_HdrWbs.ejs +2 -1
  32. package/src/emit/infrastructure/functions/functionYamlArtifact.js +3 -8
  33. package/src/emit/infrastructure/functions/templates/S3Function_Yaml.ejs +2 -3
  34. package/src/emit/infrastructure/resources/iamResourceArtifacts.js +22 -0
  35. package/src/emit/infrastructure/resources/resourceState.js +15 -5
  36. package/src/emit/infrastructure/resources/s3ResourceArtifacts.js +19 -3
  37. package/src/emit/infrastructure/resources/templates/S3Bucket_Yaml.ejs +1 -1
  38. package/src/emit/infrastructure/resources/templates/SystemDynamoDB_Yaml.ejs +2 -2
  39. package/src/emit/schemas/error/emitErrorSchemas.js +120 -10
  40. package/src/emit/schemas/error/templates/errorConsts.ejs +12 -0
  41. package/src/generate.js +32 -30
  42. package/src/plan/domainRegistry.js +2 -1
  43. package/test/integration/publicApi.test.js +33 -24
  44. package/test/unit/application/createTargetRoleHandler.test.js +33 -10
  45. package/test/unit/application/customFlowApplicationArtifacts.test.js +6 -6
  46. package/test/unit/application/customFlowS3.test.js +858 -0
  47. package/test/unit/infrastructure/functionYamlArtifact.test.js +6 -4
  48. package/test/unit/infrastructure/resourceArtifacts.test.js +51 -0
  49. package/test/unit/schemas/dynamicRbacFlowSchemas.test.js +8 -6
  50. package/test/unit/schemas/dynamicRelationshipFlowSchemas.test.js +51 -0
  51. package/test/unit/schemas/errorSchemas.test.js +84 -5
@@ -83,14 +83,16 @@ test('filters all SQS functions by their calling flow', () => {
83
83
  /#<#CreateCompleteHdrSqsFilterPatterns#>[\s\S]*filterPatterns:[\s\S]*\$\{self:custom\.iz_resourcePrefix\}CreateCompleteHdrSqs/
84
84
  );
85
85
  assert.match(completion, /#<#\/CreateCompleteHdrSqsFilterPatterns#>/);
86
- assert.match(completion, /callingFlow:\s*\n\s*- exists: false/);
87
- assert.match(completion, /MessageAttributes:\s*\n\s*- exists: false/);
86
+ assert.match(completion, /callingFlow:\s*\n\s*StringValue:/);
87
+ assert.doesNotMatch(completion, /callingFlow:\s*\n\s*- exists: false/);
88
+ assert.doesNotMatch(completion, /MessageAttributes:\s*\n\s*- exists: false/);
88
89
  assert.match(
89
90
  ordinary,
90
91
  /#<#CreateHdrSqsFilterPatterns#>[\s\S]*filterPatterns:[\s\S]*\$\{self:custom\.iz_resourcePrefix\}CreateHdrSqs/
91
92
  );
92
93
  assert.match(ordinary, /#<#\/CreateHdrSqsFilterPatterns#>/);
93
- assert.match(ordinary, /callingFlow:\s*\n\s*- exists: false/);
94
- assert.match(ordinary, /MessageAttributes:\s*\n\s*- exists: false/);
94
+ assert.match(ordinary, /callingFlow:\s*\n\s*StringValue:/);
95
+ assert.doesNotMatch(ordinary, /callingFlow:\s*\n\s*- exists: false/);
96
+ assert.doesNotMatch(ordinary, /MessageAttributes:\s*\n\s*- exists: false/);
95
97
  });
96
98
 
@@ -7,6 +7,10 @@ import test, { after } from 'node:test';
7
7
  process.env.NODE_ENV = 'test';
8
8
 
9
9
  import { generateResourceArtifacts } from '../../../src/emit/infrastructure/resources/generateResourceArtifacts.js';
10
+ import {
11
+ renderS3,
12
+ toAlphanumericPascalCase
13
+ } from '../../../src/emit/infrastructure/resources/s3ResourceArtifacts.js';
10
14
 
11
15
  const roots = new Set();
12
16
  after(() =>
@@ -909,3 +913,50 @@ async function createProject() {
909
913
  );
910
914
  return rootPath;
911
915
  }
916
+
917
+ test('toAlphanumericPascalCase converts non-alphanumeric strings to PascalCase', () => {
918
+ assert.equal(
919
+ toAlphanumericPascalCase('video-record-temp-test'),
920
+ 'VideoRecordTempTest'
921
+ );
922
+ assert.equal(
923
+ toAlphanumericPascalCase('video.record_temp-test'),
924
+ 'VideoRecordTempTest'
925
+ );
926
+ assert.equal(toAlphanumericPascalCase('AlreadyPascal'), 'AlreadyPascal');
927
+ assert.equal(toAlphanumericPascalCase(''), '');
928
+ });
929
+
930
+ test('renderS3 generates alphanumeric PascalCase Logical ID for CloudFormation', async () => {
931
+ const yaml = await renderS3({
932
+ s3Buckets: ['video-record-temp-test']
933
+ });
934
+ assert.ok(
935
+ yaml.includes('VideoRecordTempTestS3Bucket:'),
936
+ 'Logical ID must be alphanumeric PascalCase without hyphens'
937
+ );
938
+ assert.ok(
939
+ !yaml.includes('video-record-temp-testS3Bucket:'),
940
+ 'Logical ID must not contain hyphens'
941
+ );
942
+ assert.ok(
943
+ yaml.includes('BucketName: video-record-temp-test'),
944
+ 'BucketName property must preserve original bucket name'
945
+ );
946
+ });
947
+
948
+ test('roleStatements emits s3:GetObject for custom S3 flow bucket', async () => {
949
+ const rootPath = await createProject();
950
+ await generateResourceArtifacts(rootPath);
951
+ const iam = await readFile(
952
+ join(
953
+ rootPath,
954
+ 'app/sls_yaml/generatedCode/source/generate-shared-resource.yml'
955
+ ),
956
+ 'utf8'
957
+ );
958
+ const uploadRole = roleBlock(iam, 'UploadRole');
959
+ assert.match(uploadRole, /s3:GetObject/);
960
+ assert.match(uploadRole, /arn:aws:s3:::UploadBucket\/\*/);
961
+ });
962
+
@@ -26,7 +26,8 @@ const STATIC_RBAC_ACTIONS = [
26
26
  const STATIC_LIMIT_ACTIONS = [
27
27
  'ReservedDynamicUsage',
28
28
  'ConfirmUsage',
29
- 'CancelUsage'
29
+ 'CancelUsage',
30
+ 'ReservedUsageTtlExpired'
30
31
  ];
31
32
 
32
33
  after(async () => {
@@ -113,11 +114,12 @@ test('generates shared and object-specific RBAC flow schemas', async () => {
113
114
  'lambdaSyncInv',
114
115
  'lambdaSyncApi'
115
116
  ]);
116
- assert.deepEqual(cancelUsage.event, [
117
- 'ownTopic',
118
- 'lambdaSyncInv',
119
- 'lambdaSyncApi'
120
- ]);
117
+ const reservedUsageTtlExpired = await readGeneratedFlow(
118
+ rootPath,
119
+ 'ReservedUsageTtlExpired',
120
+ 'FlowLimit'
121
+ );
122
+ assert.deepEqual(reservedUsageTtlExpired.event, ['dss']);
121
123
  });
122
124
 
123
125
  test('removes RBAC schemas when the last RBAC object is removed', async () => {
@@ -6,6 +6,7 @@ import test, { after } from 'node:test';
6
6
  import { pathToFileURL } from 'node:url';
7
7
 
8
8
  import { generateDynamicRelationshipFlowSchemas } from '../../../src/emit/application/relationship/generateDynamicRelationshipFlowSchemas.js';
9
+ import { loadLocalSchemaDirectory } from '../../../src/compile/loadSchemas.js';
9
10
 
10
11
  const RELATIONSHIP_ACTIONS = [
11
12
  'CreateRelationship',
@@ -136,6 +137,56 @@ test('does not allow options to override the rootPath argument', async () => {
136
137
  );
137
138
  });
138
139
 
140
+ test('validates relationship schemas with systemNoUser and systemWithUser generatedBy', async () => {
141
+ const rootPath = await mkdtemp(
142
+ join(tmpdir(), 'generate-service-code-v2-rel-val-')
143
+ );
144
+ projectRoots.add(rootPath);
145
+ const relationshipDirectory = join(
146
+ rootPath,
147
+ 'app/src/schemas/RelationshipSchemas'
148
+ );
149
+ await mkdir(relationshipDirectory, { recursive: true });
150
+ const relSchema = [
151
+ {
152
+ isItemLabel: {
153
+ fieldNames: {},
154
+ storageResources: {
155
+ myGraph: { storageType: 'graph', graphServerTag: 'GraphHandler' }
156
+ },
157
+ generatedBy: 'systemNoUser',
158
+ links: [
159
+ {
160
+ storageResourceTags: ['myGraph'],
161
+ from: {
162
+ objType: { serviceTag: 'ItemSvc', objectType: 'itemValue' },
163
+ linkType: 'many',
164
+ requiredOnCreate: true
165
+ },
166
+ to: {
167
+ objType: { serviceTag: 'ItemSvc', objectType: 'itemLabel' },
168
+ linkType: 'one'
169
+ }
170
+ }
171
+ ]
172
+ }
173
+ }
174
+ ];
175
+ await writeFile(
176
+ join(relationshipDirectory, 'isItemLabel.js'),
177
+ `export default ${JSON.stringify(relSchema, null, 2)};\n`
178
+ );
179
+
180
+ const schemas = await loadLocalSchemaDirectory(
181
+ { schemaRoot: join(rootPath, 'app/src/schemas') },
182
+ 'RelationshipSchemas',
183
+ { validateSchemas: true }
184
+ );
185
+
186
+ assert.equal(schemas.length, 1);
187
+ assert.equal(schemas[0].isItemLabel.generatedBy, 'systemNoUser');
188
+ });
189
+
139
190
  async function createProject({ singletonExport = false } = {}) {
140
191
  const rootPath = await mkdtemp(
141
192
  join(tmpdir(), 'generate-service-code-v2-relationship-')
@@ -1,5 +1,5 @@
1
1
  import assert from 'node:assert/strict';
2
- import { readFile, rm, writeFile, mkdir } from 'node:fs/promises';
2
+ import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { tmpdir } from 'node:os';
5
5
  import test, { after } from 'node:test';
@@ -7,7 +7,10 @@ import test, { after } from 'node:test';
7
7
  import {
8
8
  emitErrorSchemas,
9
9
  errorSchemaManagedPaths,
10
- ERROR_SCHEMA_PATH
10
+ ERROR_SCHEMA_PATH,
11
+ SHARED_ERROR_PACKAGE_PATH,
12
+ SHARED_ERROR_INDEX_PATH,
13
+ SHARED_ERROR_CONSTS_PATH
11
14
  } from '../../../src/emit/schemas/error/emitErrorSchemas.js';
12
15
  import { generate } from '../../../src/generate.js';
13
16
 
@@ -49,7 +52,7 @@ test('emitErrorSchemas produces expected ERROR_TYPES for graph and general error
49
52
  ]
50
53
  });
51
54
 
52
- assert.equal(artifacts.length, 1);
55
+ assert.equal(artifacts.length, 4);
53
56
  assert.equal(artifacts[0].path, ERROR_SCHEMA_PATH);
54
57
 
55
58
  const content = artifacts[0].content;
@@ -86,6 +89,31 @@ test('emitErrorSchemas produces expected ERROR_TYPES for graph and general error
86
89
  assert.ok(customIdx < explicitIdx);
87
90
  assert.ok(explicitIdx < invalidIdx);
88
91
  assert.ok(invalidIdx < notFoundIdx);
92
+
93
+ // Shared library artifacts
94
+ assert.equal(artifacts[1].path, SHARED_ERROR_PACKAGE_PATH);
95
+ const parsedPkg = JSON.parse(artifacts[1].content);
96
+ assert.equal(
97
+ parsedPkg.name,
98
+ '@izara_project/izara-myservice-library-errorlib'
99
+ );
100
+
101
+ assert.equal(artifacts[2].path, SHARED_ERROR_INDEX_PATH);
102
+ assert.match(
103
+ artifacts[2].content,
104
+ /import \* as errorLib from '\.\/src\/errorConsts\.js';/
105
+ );
106
+
107
+ assert.equal(artifacts[3].path, SHARED_ERROR_CONSTS_PATH);
108
+ const sharedContent = artifacts[3].content;
109
+ assert.match(
110
+ sharedContent,
111
+ /\/\/ only generate error that sharedLibrary: true/
112
+ );
113
+ assert.match(sharedContent, /INVALID_GENERATED_BY:/);
114
+ assert.match(sharedContent, /NOT_FOUND_SCHEMA:/);
115
+ assert.doesNotMatch(sharedContent, /CUSTOM_INTERNAL_ERROR:/);
116
+ assert.doesNotMatch(sharedContent, /EXPLICIT_SERVICE_ERROR:/);
89
117
  });
90
118
 
91
119
  test('emitErrorSchemas returns empty array when no errors provided', async () => {
@@ -97,9 +125,18 @@ test('emitErrorSchemas returns empty array when no errors provided', async () =>
97
125
  assert.deepEqual(artifacts, []);
98
126
  assert.deepEqual(errorSchemaManagedPaths([]), []);
99
127
  assert.deepEqual(errorSchemaManagedPaths(['someError']), [ERROR_SCHEMA_PATH]);
128
+ assert.deepEqual(
129
+ errorSchemaManagedPaths([{ errorTag: 'E1', sharedLibrary: true }]),
130
+ [
131
+ ERROR_SCHEMA_PATH,
132
+ SHARED_ERROR_PACKAGE_PATH,
133
+ SHARED_ERROR_INDEX_PATH,
134
+ SHARED_ERROR_CONSTS_PATH
135
+ ]
136
+ );
100
137
  });
101
138
 
102
- test('generate creates errorSchema.js when ErrorSchemas directory exists', async () => {
139
+ test('generate creates errorConsts.js and libs/errorLib when ErrorSchemas directory exists', async () => {
103
140
  const rootPath = join(
104
141
  tmpdir(),
105
142
  `error-schema-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
@@ -141,7 +178,7 @@ test('generate creates errorSchema.js when ErrorSchemas directory exists', async
141
178
 
142
179
  const targetFile = join(
143
180
  rootPath,
144
- 'app/src/schemas/ErrorSchemas/generateCode/errorSchema.js'
181
+ 'app/src/schemas/ErrorSchemas/generateCode/errorConsts.js'
145
182
  );
146
183
  const content = await readFile(targetFile, 'utf8');
147
184
 
@@ -151,4 +188,46 @@ test('generate creates errorSchema.js when ErrorSchemas directory exists', async
151
188
  assert.match(content, /MISSING_USER_NODE:/);
152
189
  assert.match(content, /serviceTag:\s*'CategoryTreeStandard'/);
153
190
  assert.doesNotMatch(content, /export default/);
191
+
192
+ // Verify libs/errorLib files
193
+ const pkgFile = join(rootPath, 'libs/errorLib/package.json');
194
+ const pkgContent = JSON.parse(await readFile(pkgFile, 'utf8'));
195
+ assert.equal(
196
+ pkgContent.name,
197
+ '@izara_project/izara-categorytreestandard-library-errorlib'
198
+ );
199
+
200
+ const indexFile = join(rootPath, 'libs/errorLib/index.js');
201
+ const indexContent = await readFile(indexFile, 'utf8');
202
+ assert.match(indexContent, /export { errorLib };/);
203
+
204
+ const sharedConstsFile = join(rootPath, 'libs/errorLib/src/errorConsts.js');
205
+ const sharedConstsContent = await readFile(sharedConstsFile, 'utf8');
206
+ assert.match(
207
+ sharedConstsContent,
208
+ /\/\/ only generate error that sharedLibrary: true/
209
+ );
210
+ assert.match(sharedConstsContent, /NOT_FOUND_SCHEMA:/);
211
+ assert.match(sharedConstsContent, /INVALID_GENERATED_BY:/);
212
+ assert.match(sharedConstsContent, /MISSING_USER_NODE:/);
213
+ });
214
+
215
+ test('generate creates ErrorSchemas directory if it does not exist', async () => {
216
+ const rootPath = join(
217
+ tmpdir(),
218
+ `error-schema-dir-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
219
+ );
220
+ roots.add(rootPath);
221
+ await mkdir(join(rootPath, 'config'), { recursive: true });
222
+
223
+ await writeFile(
224
+ join(rootPath, 'config/serverless.config.yml'),
225
+ 'main_config:\n iz_serviceTag: TestService\n'
226
+ );
227
+
228
+ await generate(rootPath);
229
+
230
+ const errorSchemasDir = join(rootPath, 'app/src/schemas/ErrorSchemas');
231
+ const dirStat = await stat(errorSchemasDir);
232
+ assert.ok(dirStat.isDirectory());
154
233
  });