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

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 (86) hide show
  1. package/.prettierrc +11 -0
  2. package/package.json +16 -12
  3. package/scripts/format-ejs.js +105 -0
  4. package/src/compile/compileProject.js +16 -1
  5. package/src/compile/loadSchemas.js +89 -4
  6. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +1 -2
  7. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +296 -105
  8. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +222 -144
  9. package/src/emit/crud/templates/FlowEndpointBeforeLogical_Main.ejs +22 -6
  10. package/src/emit/crud/templates/FlowEndpointComplete_Main.ejs +134 -43
  11. package/src/emit/crud/templates/FlowEndpoint_HdrWbs.ejs +51 -31
  12. package/src/emit/crud/templates/FlowHandler_HdrApi.ejs +53 -45
  13. package/src/emit/crud/templates/FlowHandler_HdrInv.ejs +28 -26
  14. package/src/emit/crud/templates/FlowHandler_HdrSqs.ejs +15 -11
  15. package/src/emit/crud/templates/FlowMain_Wbs.ejs +53 -38
  16. package/src/emit/crud/templates/GetEndpoint_Main.ejs +5 -2
  17. package/src/emit/crud/templates/GetRangeEndpoint_Main.ejs +2 -2
  18. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +402 -249
  19. package/src/emit/custom/customFlows.js +51 -21
  20. package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +13 -18
  21. package/src/emit/custom/templates/FlowEndpoint_Main.ejs +48 -36
  22. package/src/emit/custom/templates/FlowStep_Main.ejs +7 -9
  23. package/src/emit/custom/templates/s3/ConfirmReserved_HdrSqs.ejs +59 -54
  24. package/src/emit/custom/templates/s3/ConfirmReserved_Main.ejs +33 -23
  25. package/src/emit/custom/templates/s3/CreateObject_HdrWbs.ejs +30 -23
  26. package/src/emit/custom/templates/s3/CreateObject_Main.ejs +56 -39
  27. package/src/emit/custom/templates/s3/CreatePreSignUrl_HdrSqs.ejs +56 -51
  28. package/src/emit/custom/templates/s3/CreatePreSignUrl_Main.ejs +112 -75
  29. package/src/emit/custom/templates/s3/FlowHandler_HdrS3.ejs +12 -7
  30. package/src/emit/custom/templates/s3/ProcessS3File_Main.ejs +11 -8
  31. package/src/emit/custom/templates/s3/ReservedLimit_HdrSqs.ejs +64 -55
  32. package/src/emit/custom/templates/s3/ReservedLimit_Main.ejs +74 -52
  33. package/src/emit/custom/templates/triggerCache/CheckTriggerCache_HdrSqs.ejs +58 -14
  34. package/src/emit/custom/templates/triggerCache/CheckTriggerCache_Main.ejs +12 -17
  35. package/src/emit/custom/templates/triggerCache/TriggerCacheComplete_Main.ejs +20 -13
  36. package/src/emit/custom/templates/triggerCache/TriggerCache_Main.ejs +4 -1
  37. package/src/emit/function/functionYamlArtifact.js +20 -0
  38. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +28 -15
  39. package/src/emit/rbac/emitRbacApplicationArtifacts.js +168 -69
  40. package/src/emit/rbac/templates/CreateTargetRole_HdrSqs.ejs +41 -18
  41. package/src/emit/rbac/templates/CreateTargetRole_Main.ejs +23 -3
  42. package/src/emit/rbac/templates/ListRbac_HdrApi.ejs +27 -5
  43. package/src/emit/rbac/templates/ListRbac_HdrSqs.ejs +41 -8
  44. package/src/emit/rbac/templates/ListRbac_Main.ejs +26 -6
  45. package/src/emit/rbac/templates/RbacMutation_Main.ejs +23 -3
  46. package/src/emit/rbac/templates/UserRbacFlow_HdrSqs.ejs +46 -0
  47. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +152 -33
  48. package/src/emit/rbac/templates/UserRoleMutation_Main.ejs +24 -4
  49. package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +1 -2
  50. package/src/emit/relationship/templates/ChangeRelationshipComplete_Main.ejs +64 -37
  51. package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +258 -142
  52. package/src/emit/relationship/templates/CreateRelationshipComplete_Main.ejs +68 -43
  53. package/src/emit/relationship/templates/CreateRelationship_Main.ejs +315 -171
  54. package/src/emit/relationship/templates/DeleteRelationshipComplete_Main.ejs +67 -41
  55. package/src/emit/relationship/templates/DeleteRelationship_Main.ejs +226 -115
  56. package/src/emit/relationship/templates/FlowHandler_HdrApi.ejs +41 -33
  57. package/src/emit/relationship/templates/FlowHandler_HdrDsq.ejs +15 -11
  58. package/src/emit/relationship/templates/FlowHandler_HdrInv.ejs +28 -26
  59. package/src/emit/relationship/templates/FlowHandler_HdrSqs.ejs +15 -11
  60. package/src/emit/relationship/templates/GetRelationshipComplete_Main.ejs +46 -26
  61. package/src/emit/relationship/templates/GetRelationship_Main.ejs +233 -126
  62. package/src/emit/relationship/templates/MoveRelationshipComplete_Main.ejs +72 -44
  63. package/src/emit/relationship/templates/MoveRelationship_Main.ejs +266 -150
  64. package/src/emit/relationship/templates/UpdateRelationshipComplete_Main.ejs +74 -45
  65. package/src/emit/relationship/templates/UpdateRelationship_Main.ejs +285 -152
  66. package/src/emit/resource/emitResourceArtifacts.js +107 -11
  67. package/src/emit/resource/templates/DynamoDBTable_Yaml.ejs +18 -18
  68. package/src/emit/resource/templates/FlowOut_Yaml.ejs +5 -5
  69. package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +138 -0
  70. package/src/emit/resource/templates/ResourceBeforeLogicalYaml.ejs +44 -44
  71. package/src/emit/resource/templates/ResourceSqsYaml.ejs +36 -36
  72. package/src/emit/resource/templates/ResourceYaml.ejs +48 -48
  73. package/src/emit/resource/templates/S3Bucket_Yaml.ejs +19 -0
  74. package/src/emit/resource/templates/SharedResource_Yaml.ejs +11 -11
  75. package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +105 -105
  76. package/src/generate.js +6 -2
  77. package/test/createTargetRoleHandler.test.js +63 -14
  78. package/test/customFlowApplicationArtifacts.test.js +48 -23
  79. package/test/dynamicCrudFlowSchemas.test.js +6 -6
  80. package/test/dynamicRbacFlowSchemas.test.js +8 -12
  81. package/test/dynamicRelationshipFlowSchemas.test.js +3 -3
  82. package/test/publicApi.test.js +10 -12
  83. package/test/resourceArtifacts.test.js +12 -2
  84. package/docs/test.png +0 -0
  85. package/src/emit/rbac/templates/UserRbacFlow_HdrInv.ejs +0 -10
  86. /package/docs/{test.mmd → TriggeredCacheFlow.mmd} +0 -0
package/.prettierrc ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "printWidth": 80,
3
+ "tabWidth": 2,
4
+ "useTabs": false,
5
+ "semi": true,
6
+ "singleQuote": true,
7
+ "trailingComma": "none",
8
+ "bracketSpacing": true,
9
+ "endOfLine": "lf",
10
+ "plugins": ["prettier-plugin-ejs"]
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izara_project/izara-core-generate-service-code",
3
- "version": "1.0.67",
3
+ "version": "1.0.68",
4
4
  "description": "Code for locally generating per service files",
5
5
  "homepage": "https://bitbucket.org/izara-core-support-services/izara-core-support-services-generate-service-code#readme",
6
6
  "repository": {
@@ -10,31 +10,35 @@
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "author": "Sven Mason <thebarbariansven@gmail.com>",
12
12
  "type": "module",
13
- "main": "index.js",
14
13
  "exports": "./index.js",
14
+ "main": "index.js",
15
15
  "scripts": {
16
- "test": "jest"
16
+ "test": "jest",
17
+ "format:ejs": "node scripts/format-ejs.js"
17
18
  },
18
19
  "jest": {
19
20
  "testEnvironment": "node"
20
21
  },
21
22
  "dependencies": {
23
+ "@izara_project/izara-attribute-tree": "^1.0.15",
22
24
  "@izara_project/izara-core-library-core": "^1.0.32",
23
- "@izara_project/izara-core-library-logger": "^1.0.11",
25
+ "@izara_project/izara-property-nodes": "^1.0.16",
24
26
  "@izara_project/izara-shared-core": "^1.0.13",
25
27
  "ejs": "^6.0.1",
26
28
  "js-beautify": "^2.0.3",
27
- "yaml": "^2.9.0",
28
- "@izara_project/izara-property-nodes": "^1.0.16",
29
- "@izara_project/izara-attribute-tree": "^1.0.15"
29
+ "yaml": "^2.9.0"
30
+ },
31
+ "devDependencies": {
32
+ "@izara_project/izara-core-library-logger": "^1.0.11",
33
+ "@izara_project/izara-core-library-service-schemas": "1.0.158",
34
+ "@izara_project/izara-shared-service-schemas": "^1.0.67",
35
+ "jest": "^30.4.2",
36
+ "prettier": "^3.9.6",
37
+ "prettier-plugin-ejs": "^1.0.3"
30
38
  },
31
39
  "peerDependencies": {
40
+ "@izara_project/izara-core-library-logger": "^1.0.11",
32
41
  "@izara_project/izara-core-library-service-schemas": "^1.0.157",
33
42
  "@izara_project/izara-shared-service-schemas": "^1.0.65"
34
- },
35
- "devDependencies": {
36
- "@izara_project/izara-core-library-service-schemas": "1.0.158",
37
- "@izara_project/izara-shared-service-schemas": "^1.0.66",
38
- "jest": "^30.4.2"
39
43
  }
40
44
  }
@@ -0,0 +1,105 @@
1
+ /* Copyright (C) 2026 Sven Mason <http://izara.io>
2
+ * Prettier formatting script for EJS template files under src/emit
3
+ */
4
+
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import prettier from 'prettier';
8
+
9
+ function getAllEjsFiles(dirPath, arrayOfFiles = []) {
10
+ const files = fs.readdirSync(dirPath);
11
+ for (const file of files) {
12
+ const fullPath = path.join(dirPath, file);
13
+ if (fs.statSync(fullPath).isDirectory()) {
14
+ arrayOfFiles = getAllEjsFiles(fullPath, arrayOfFiles);
15
+ } else if (file.endsWith('.ejs')) {
16
+ arrayOfFiles.push(fullPath);
17
+ }
18
+ }
19
+ return arrayOfFiles;
20
+ }
21
+
22
+ export async function formatEjsFiles(targetDir = 'src/emit') {
23
+ const ejsFiles = getAllEjsFiles(targetDir);
24
+ let updatedCount = 0;
25
+
26
+ for (const filePath of ejsFiles) {
27
+ const content = fs.readFileSync(filePath, 'utf8');
28
+ const isYaml = filePath.includes('Yaml.ejs') || filePath.includes('Yaml');
29
+
30
+ const placeholders = [];
31
+ let masked = content;
32
+
33
+ masked = masked.replace(/<%[_-]?\s*([\s\S]*?)\s*[_-]?%>/g, (match) => {
34
+ const id = placeholders.length;
35
+ const isExpr = match.startsWith('<%=') || match.startsWith('<%-');
36
+ const isConditionalComma =
37
+ match.includes('?') &&
38
+ (match.includes("','") || match.includes('","'));
39
+
40
+ let replacement;
41
+ if (isYaml) {
42
+ replacement = isExpr ? `EJS_VAL_${id}` : `# __EJS_TAG_${id}__`;
43
+ } else {
44
+ if (isConditionalComma) {
45
+ replacement = `, /*__EJS_COMMA_${id}__*/`;
46
+ } else if (isExpr) {
47
+ replacement = `__EJS_VAR_${id}__`;
48
+ } else {
49
+ replacement = `/*__EJS_STMT_${id}__*/`;
50
+ }
51
+ }
52
+ placeholders.push({
53
+ id,
54
+ original: match,
55
+ replacement,
56
+ isConditionalComma
57
+ });
58
+ return replacement;
59
+ });
60
+
61
+ const parser = isYaml ? 'yaml' : 'babel';
62
+ let formatted = await prettier.format(masked, {
63
+ parser,
64
+ singleQuote: true,
65
+ semi: true,
66
+ tabWidth: 2,
67
+ useTabs: false,
68
+ trailingComma: 'none',
69
+ printWidth: 80,
70
+ endOfLine: 'lf'
71
+ });
72
+
73
+ for (const item of placeholders) {
74
+ if (item.isConditionalComma) {
75
+ formatted = formatted.replace(item.replacement, item.original);
76
+ formatted = formatted.replace(
77
+ `, /*__EJS_COMMA_${item.id}__*/`,
78
+ item.original
79
+ );
80
+ } else {
81
+ formatted = formatted.replace(item.replacement, item.original);
82
+ }
83
+ }
84
+
85
+ // Ensure unix newline at EOF
86
+ if (!formatted.endsWith('\n')) {
87
+ formatted += '\n';
88
+ }
89
+
90
+ if (formatted !== content) {
91
+ fs.writeFileSync(filePath, formatted, 'utf8');
92
+ updatedCount++;
93
+ }
94
+ }
95
+
96
+ return { total: ejsFiles.length, updated: updatedCount };
97
+ }
98
+
99
+ if (process.argv[1] === new URL(import.meta.url).pathname) {
100
+ formatEjsFiles().then(({ total, updated }) => {
101
+ console.log(
102
+ `Successfully formatted ${updated} / ${total} EJS template files in src/emit.`
103
+ );
104
+ });
105
+ }
@@ -1,4 +1,6 @@
1
1
  import { join, resolve } from 'node:path';
2
+ import { readFileSync, existsSync } from 'node:fs';
3
+ import { parse as parseYaml } from 'yaml';
2
4
 
3
5
  export function compileProject(request = {}) {
4
6
  if (!isNonEmptyString(request.rootPath)) {
@@ -11,12 +13,25 @@ export function compileProject(request = {}) {
11
13
  request.configPath ?? join(rootPath, 'config', 'serverless.config.yml')
12
14
  );
13
15
 
16
+ let serviceTag = request.serviceTag;
17
+ if (!serviceTag && existsSync(configPath)) {
18
+ try {
19
+ const configContent = readFileSync(configPath, 'utf8');
20
+ const parsedConfig = parseYaml(configContent);
21
+ if (parsedConfig && parsedConfig.main_config) {
22
+ serviceTag = parsedConfig.main_config.iz_serviceTag || parsedConfig.main_config.iz_serviceName;
23
+ }
24
+ } catch (err) {
25
+ // Ignore configuration read error
26
+ }
27
+ }
28
+
14
29
  return Object.freeze({
15
30
  rootPath,
16
31
  outputPath,
17
32
  configPath,
18
33
  schemaRoot: join(rootPath, 'app', 'src', 'schemas'),
19
- serviceTag: request.serviceTag,
34
+ serviceTag,
20
35
  schemaTemplateConfig: request.schemaTemplateConfig ?? []
21
36
  });
22
37
  }
@@ -1,7 +1,8 @@
1
1
  import { readdir, stat } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
- import { utils } from '@izara_project/izara-core-library-service-schemas';
4
+ import { getObjectSchema, utils } from '@izara_project/izara-core-library-service-schemas';
5
+ import Logger from '@izara_project/izara-core-library-logger';
5
6
 
6
7
  export async function loadLocalSchemaDirectory(project, directoryName) {
7
8
  const directoryPath = join(project.schemaRoot, directoryName);
@@ -31,11 +32,15 @@ export async function loadLocalSchemaDirectory(project, directoryName) {
31
32
  let errors = [];
32
33
 
33
34
  if (directoryName === 'ObjectSchemas') {
34
- [status, errors] = utils.validateBasicObjectSchema({}, schema);
35
+ [status, errors] = utils.validateObjectSchema({ logger: Logger }, schema);
35
36
  } else if (directoryName === 'RelationshipSchemas') {
36
- [status, errors] = utils.validateRelationshipSchema({}, schema);
37
+ if (entry.name.toLowerCase() === 'refobjectrelationships.js') {
38
+ [status, errors] = utils.validateRefRelationshipSchema({ logger: Logger }, schema);
39
+ } else {
40
+ [status, errors] = utils.validateRelationshipSchema({ logger: Logger }, schema);
41
+ }
37
42
  } else if (directoryName === 'FlowSchemas') {
38
- [status, errors] = utils.validateBasicFlowSchema({}, schema);
43
+ [status, errors] = utils.validateBasicFlowSchema({ logger: Logger }, schema);
39
44
  }
40
45
 
41
46
  if (!status) {
@@ -48,3 +53,83 @@ export async function loadLocalSchemaDirectory(project, directoryName) {
48
53
  }
49
54
  return schemas;
50
55
  }
56
+
57
+ /**
58
+ * Resolves extendObjType for ObjectSchemas by fetching the parent schema
59
+ * from S3 and merging its storageResources into the child schema.
60
+ *
61
+ * Only schemas with `extendObjType` are processed; others are returned as-is.
62
+ * If `bucketName` is not provided, falls back to the
63
+ * `iz_serviceSchemaBucketName` environment variable.
64
+ *
65
+ * @param {Object[]} objectSchemas - Array of local ObjectSchemas from
66
+ * loadLocalSchemaDirectory.
67
+ * @param {string} [bucketName] - S3 bucket name for fetching parent schemas.
68
+ * @returns {Promise<Object[]>} The same array with extended storageResources
69
+ * merged in.
70
+ */
71
+ export async function resolveExtendedObjectSchemas(
72
+ objectSchemas,
73
+ bucketName
74
+ ) {
75
+ if (!Array.isArray(objectSchemas)) {
76
+ throw new TypeError('objectSchemas must be an array');
77
+ }
78
+ const resolvedSchemas = [];
79
+ for (const schema of objectSchemas) {
80
+ if (!schema.hasOwnProperty('extendObjType')) {
81
+ resolvedSchemas.push(schema);
82
+ continue;
83
+ }
84
+ try {
85
+ // Track which storageResource keys existed in the child before merge
86
+ const childStorageResourceKeys = new Set(
87
+ Object.keys(schema.storageResources ?? {})
88
+ );
89
+ const parentServiceTag = schema.extendObjType.serviceTag;
90
+
91
+ const mergedSchema = await getObjectSchema.mergeExtendObjSchema(
92
+ { logger: Logger },
93
+ schema,
94
+ bucketName
95
+ );
96
+
97
+ // Separate parent DynamoDB resources from child's own storageResources.
98
+ // Parent DynamoDB tables exist in the parent service — they must NOT be
99
+ // re-created here. Instead, track them as external references so that
100
+ // emitResourceArtifacts can add cross-service ARNs to IAM roles.
101
+ const externalDynamoResources = [];
102
+ const ownStorageResources = {};
103
+ for (const [key, resource] of Object.entries(
104
+ mergedSchema.storageResources ?? {}
105
+ )) {
106
+ if (
107
+ resource?.storageType === 'dynamoDB' &&
108
+ !childStorageResourceKeys.has(key)
109
+ ) {
110
+ externalDynamoResources.push({
111
+ serviceTag: parentServiceTag,
112
+ tableName: resource.tableName
113
+ });
114
+ } else {
115
+ ownStorageResources[key] = resource;
116
+ }
117
+ }
118
+
119
+ mergedSchema.storageResources = ownStorageResources;
120
+ if (externalDynamoResources.length > 0) {
121
+ mergedSchema.externalDynamoResources = externalDynamoResources;
122
+ }
123
+
124
+ resolvedSchemas.push(mergedSchema);
125
+ } catch (error) {
126
+ Logger.error(
127
+ `resolveExtendedObjectSchemas: failed to merge extendObjType` +
128
+ ` for objectType=${schema.objectType}`,
129
+ { error }
130
+ );
131
+ throw error;
132
+ }
133
+ }
134
+ return resolvedSchemas;
135
+ }
@@ -4,7 +4,7 @@ const ACTIONS = ['Create', 'Update', 'Delete', 'Get'];
4
4
  const MESSAGE_ATTRIBUTES = ['userId', 'correlationId'];
5
5
 
6
6
  export const dynamicCrudFlowSchemaPaths = ACTIONS.map(
7
- action => `app/src/schemas/FlowSchemas/generateCode/${action}.js`
7
+ action => `app/src/schemas/FlowSchemas/generateCode/FlowObjects/${action}.js`
8
8
  );
9
9
 
10
10
  export function emitDynamicCrudFlowSchemas({ objects, flows = [] }) {
@@ -59,7 +59,6 @@ function createDynamicFlow(action, hasBeforeLogicalStep) {
59
59
 
60
60
  return {
61
61
  flowTag: action,
62
- isDynamicObjectType: true,
63
62
  initiateBy: 'system',
64
63
  handleObj: 'one',
65
64
  statusType: 'none',