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

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izara_project/izara-core-generate-service-code",
3
- "version": "1.0.92",
3
+ "version": "1.0.93",
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": {
@@ -186,6 +186,24 @@ export async function loadLocalSchemaDirectory(
186
186
  );
187
187
  if (loadedSchemas.length === 0) continue;
188
188
 
189
+ if (directoryName === 'ErrorSchemas') {
190
+ for (const schema of loadedSchemas) {
191
+ if (!schema._sourceFile) {
192
+ schema._sourceFile = entry.name;
193
+ }
194
+ if (!schema.errorTag || typeof schema.errorTag !== 'string') {
195
+ throw new Error(
196
+ `Invalid ErrorSchema in ${entry.name}: errorTag is required and must be a non-empty string`
197
+ );
198
+ }
199
+ if (!schema.errorAction || typeof schema.errorAction !== 'string') {
200
+ throw new Error(
201
+ `Invalid ErrorSchema in ${entry.name}: errorAction is required and must be a non-empty string`
202
+ );
203
+ }
204
+ }
205
+ }
206
+
189
207
  if (directoryName === 'FlowSchemas') {
190
208
  for (const schema of loadedSchemas) {
191
209
  if (
@@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
15
15
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import { uploadObjSchema } from '@izara_project/izara-core-library-service-schemas';
18
+ import initialSetupSharedLib from '@izara_project/izara-core-library-initial-setup';
19
19
 
20
20
  const SERVICE_TAG = {
21
21
  TRANSLATIONS: 'Translations',
@@ -39,7 +39,7 @@ const uploadSchema = async (_izContext) => {
39
39
  try {
40
40
  _izContext.logger.debug('uploadSchema _izContext: ', _izContext);
41
41
 
42
- await uploadObjSchema.uploadObjectSchemaByUseCase(_izContext, {
42
+ await initialSetupSharedLib.uploadObjectSchema(_izContext, {
43
43
  ignoreExternalServices: ignoreExternalServices
44
44
  });
45
45
  return 'upload successfully';
@@ -0,0 +1,73 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import ejsLib from 'ejs';
3
+
4
+ const TEMPLATE_URL = new URL('./templates/', import.meta.url);
5
+ export const ERROR_SCHEMA_PATH =
6
+ 'app/src/schemas/ErrorSchemas/generateCode/errorSchema.js';
7
+
8
+ export function errorSchemaManagedPaths(errors = []) {
9
+ if (!errors || errors.length === 0) {
10
+ return [];
11
+ }
12
+ return [ERROR_SCHEMA_PATH];
13
+ }
14
+
15
+ /**
16
+ * Emits errorSchema.js artifact based on loaded ErrorSchemas
17
+ *
18
+ * @param {Object} params
19
+ * @param {Object} params.project
20
+ * @param {Array} [params.errors=[]]
21
+ * @param {Array} [params.objectGraphServiceTags=[]]
22
+ * @returns {Promise<Array<{path: string, content: string}>>}
23
+ */
24
+ export async function emitErrorSchemas({
25
+ project = {},
26
+ errors = [],
27
+ objectGraphServiceTags = []
28
+ } = {}) {
29
+ const safeErrors = Array.isArray(errors) ? errors : [];
30
+ if (safeErrors.length === 0) {
31
+ return [];
32
+ }
33
+
34
+ const defaultGraphServiceTag =
35
+ objectGraphServiceTags[0] || 'GraphHandler';
36
+ const defaultServiceTag = project?.serviceTag || '';
37
+
38
+ const errorTypes = {};
39
+ for (const item of safeErrors) {
40
+ if (!item || typeof item !== 'object' || !item.errorTag) {
41
+ continue;
42
+ }
43
+ const errorTag = item.errorTag;
44
+ const serviceTag = item.serviceTag || defaultServiceTag;
45
+ errorTypes[errorTag] = {
46
+ errorTag,
47
+ serviceTag
48
+ };
49
+ }
50
+
51
+ const sortedKeys = Object.keys(errorTypes).sort();
52
+ if (sortedKeys.length === 0) {
53
+ return [];
54
+ }
55
+
56
+ const sortedErrorTypes = {};
57
+ for (const key of sortedKeys) {
58
+ sortedErrorTypes[key] = errorTypes[key];
59
+ }
60
+
61
+ const templatePath = new URL('errorSchema.ejs', TEMPLATE_URL);
62
+ const template = await readFile(templatePath, 'utf8');
63
+ const content = ejsLib.render(template, {
64
+ errorTypes: sortedErrorTypes
65
+ });
66
+
67
+ return [
68
+ {
69
+ path: ERROR_SCHEMA_PATH,
70
+ content
71
+ }
72
+ ];
73
+ }
@@ -0,0 +1,8 @@
1
+ export const ERROR_TYPES = {
2
+ <% Object.keys(errorTypes).forEach((key, index, arr) => { -%>
3
+ <%- key %>: {
4
+ errorTag: '<%- errorTypes[key].errorTag %>',
5
+ serviceTag: '<%- errorTypes[key].serviceTag %>'
6
+ }<%= index < arr.length - 1 ? ',' : '' %>
7
+ <% }); -%>
8
+ };
package/src/generate.js CHANGED
@@ -61,6 +61,10 @@ import {
61
61
  attributeTreeObjectSchemaManagedPaths,
62
62
  attributeTreeRelationshipSchemaManagedPaths
63
63
  } from './emit/attributeTree/emitAttributeTreeSchemas.js';
64
+ import {
65
+ emitErrorSchemas,
66
+ errorSchemaManagedPaths
67
+ } from './emit/error/emitErrorSchemas.js';
64
68
 
65
69
  export async function generate(rootPath, options = {}) {
66
70
  const startedAt = Date.now();
@@ -70,11 +74,18 @@ export async function generate(rootPath, options = {}) {
70
74
  const schemaLoadOptions = {
71
75
  skipValidation: options.skipValidation
72
76
  };
73
- const [rawObjects, relationships, flows, notifications] = await Promise.all([
77
+ const [
78
+ rawObjects,
79
+ relationships,
80
+ flows,
81
+ notifications,
82
+ errors
83
+ ] = await Promise.all([
74
84
  loadLocalSchemaDirectory(project, 'ObjectSchemas', schemaLoadOptions),
75
85
  loadLocalSchemaDirectory(project, 'RelationshipSchemas', schemaLoadOptions),
76
86
  loadLocalSchemaDirectory(project, 'FlowSchemas', schemaLoadOptions),
77
- loadLocalSchemaDirectory(project, 'NotificationSchemas', schemaLoadOptions)
87
+ loadLocalSchemaDirectory(project, 'NotificationSchemas', schemaLoadOptions),
88
+ loadLocalSchemaDirectory(project, 'ErrorSchemas', schemaLoadOptions)
78
89
  ]);
79
90
  const schemaBucketName =
80
91
  options.schemaBucketName || process.env.iz_serviceSchemaBucketName;
@@ -93,14 +104,24 @@ export async function generate(rootPath, options = {}) {
93
104
  console.log(
94
105
  `[DEBUG] [Generate] STATUS=SCHEMAS_LOADED | ` +
95
106
  `objects=${objects.length} ` +
96
- `relationships=${relationships.length} flows=${flows.length}`
107
+ `relationships=${relationships.length} flows=${flows.length} ` +
108
+ `notifications=${notifications.length} ` +
109
+ `errors=${errors.length}`
97
110
  );
111
+ for (const objectSchema of objects) {
112
+ const objectType = objectSchema.objectType || 'unknown';
113
+ console.log(
114
+ `[DEBUG] [Generate] OBJECT_SCHEMA | objectType=${objectType}\n` +
115
+ JSON.stringify(objectSchema, null, 2)
116
+ );
117
+ }
98
118
  const groups = await _emitGroups({
99
119
  project,
100
120
  objects,
101
121
  relationships,
102
122
  flows,
103
123
  notifications,
124
+ errors,
104
125
  objectGraphServiceTags,
105
126
  relationshipGraphServiceTags,
106
127
  logger: options.logger
@@ -149,6 +170,7 @@ async function _emitGroups({
149
170
  relationships,
150
171
  flows,
151
172
  notifications,
173
+ errors,
152
174
  objectGraphServiceTags,
153
175
  relationshipGraphServiceTags,
154
176
  logger
@@ -161,7 +183,8 @@ async function _emitGroups({
161
183
  findData,
162
184
  resource,
163
185
  coreFunction,
164
- systemText
186
+ systemText,
187
+ errorSchemas
165
188
  ] = await Promise.all([
166
189
  emitCrudApplicationArtifacts({
167
190
  objects,
@@ -196,6 +219,11 @@ async function _emitGroups({
196
219
  relationships,
197
220
  flows,
198
221
  notifications
222
+ }),
223
+ emitErrorSchemas({
224
+ project,
225
+ errors,
226
+ objectGraphServiceTags
199
227
  })
200
228
  ]);
201
229
  const custom = await emitCustomFlowApplicationArtifacts(flows, {
@@ -307,6 +335,11 @@ async function _emitGroups({
307
335
  scope: 'system-text-schemas',
308
336
  managedPaths: systemTextSchemaManagedPaths(),
309
337
  artifacts: systemText
338
+ },
339
+ {
340
+ scope: 'error-schemas',
341
+ managedPaths: errorSchemaManagedPaths(errors),
342
+ artifacts: errorSchemas
310
343
  }
311
344
  ];
312
345
  }
@@ -0,0 +1,154 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFile, rm, writeFile, mkdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import test, { after } from 'node:test';
6
+
7
+ import {
8
+ emitErrorSchemas,
9
+ errorSchemaManagedPaths,
10
+ ERROR_SCHEMA_PATH
11
+ } from '../src/emit/error/emitErrorSchemas.js';
12
+ import { generate } from '../src/generate.js';
13
+
14
+ const roots = new Set();
15
+ after(() =>
16
+ Promise.all(
17
+ [...roots].map((root) => rm(root, { recursive: true, force: true }))
18
+ )
19
+ );
20
+
21
+ test('emitErrorSchemas produces expected ERROR_TYPES for graph and general errors', async () => {
22
+ const artifacts = await emitErrorSchemas({
23
+ project: { serviceTag: 'MyService' },
24
+ objectGraphServiceTags: ['GraphHandler'],
25
+ errors: [
26
+ {
27
+ errorTag: 'NOT_FOUND_SCHEMA',
28
+ errorAction: 'throw',
29
+ sharedLibrary: true,
30
+ _sourceFile: 'errorgraph.js'
31
+ },
32
+ {
33
+ errorTag: 'INVALID_GENERATED_BY',
34
+ errorAction: 'throw',
35
+ sharedLibrary: true,
36
+ _sourceFile: 'errorgraph.js'
37
+ },
38
+ {
39
+ errorTag: 'CUSTOM_INTERNAL_ERROR',
40
+ errorAction: 'return',
41
+ _sourceFile: 'errors.js'
42
+ },
43
+ {
44
+ errorTag: 'EXPLICIT_SERVICE_ERROR',
45
+ errorAction: 'throw',
46
+ serviceTag: 'CustomService',
47
+ _sourceFile: 'custom.js'
48
+ }
49
+ ]
50
+ });
51
+
52
+ assert.equal(artifacts.length, 1);
53
+ assert.equal(artifacts[0].path, ERROR_SCHEMA_PATH);
54
+
55
+ const content = artifacts[0].content;
56
+ assert.match(content, /^export const ERROR_TYPES = {/m);
57
+ assert.doesNotMatch(content, /export default/);
58
+
59
+ // Errors without explicit serviceTag resolve to project.serviceTag
60
+ assert.match(
61
+ content,
62
+ /NOT_FOUND_SCHEMA:\s*{\s*errorTag:\s*'NOT_FOUND_SCHEMA',\s*serviceTag:\s*'MyService'\s*}/
63
+ );
64
+ assert.match(
65
+ content,
66
+ /INVALID_GENERATED_BY:\s*{\s*errorTag:\s*'INVALID_GENERATED_BY',\s*serviceTag:\s*'MyService'\s*}/
67
+ );
68
+
69
+ // General error resolved to project.serviceTag
70
+ assert.match(
71
+ content,
72
+ /CUSTOM_INTERNAL_ERROR:\s*{\s*errorTag:\s*'CUSTOM_INTERNAL_ERROR',\s*serviceTag:\s*'MyService'\s*}/
73
+ );
74
+
75
+ // Explicit serviceTag respected
76
+ assert.match(
77
+ content,
78
+ /EXPLICIT_SERVICE_ERROR:\s*{\s*errorTag:\s*'EXPLICIT_SERVICE_ERROR',\s*serviceTag:\s*'CustomService'\s*}/
79
+ );
80
+
81
+ // Keys sorted alphabetically
82
+ const customIdx = content.indexOf('CUSTOM_INTERNAL_ERROR');
83
+ const explicitIdx = content.indexOf('EXPLICIT_SERVICE_ERROR');
84
+ const invalidIdx = content.indexOf('INVALID_GENERATED_BY');
85
+ const notFoundIdx = content.indexOf('NOT_FOUND_SCHEMA');
86
+ assert.ok(customIdx < explicitIdx);
87
+ assert.ok(explicitIdx < invalidIdx);
88
+ assert.ok(invalidIdx < notFoundIdx);
89
+ });
90
+
91
+ test('emitErrorSchemas returns empty array when no errors provided', async () => {
92
+ const artifacts = await emitErrorSchemas({
93
+ project: { serviceTag: 'MyService' },
94
+ errors: []
95
+ });
96
+
97
+ assert.deepEqual(artifacts, []);
98
+ assert.deepEqual(errorSchemaManagedPaths([]), []);
99
+ assert.deepEqual(errorSchemaManagedPaths(['someError']), [ERROR_SCHEMA_PATH]);
100
+ });
101
+
102
+ test('generate creates errorSchema.js when ErrorSchemas directory exists', async () => {
103
+ const rootPath = join(
104
+ tmpdir(),
105
+ `error-schema-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
106
+ );
107
+ roots.add(rootPath);
108
+ await mkdir(join(rootPath, 'app/src/schemas/ErrorSchemas'), {
109
+ recursive: true
110
+ });
111
+ await mkdir(join(rootPath, 'config'), { recursive: true });
112
+
113
+ await writeFile(
114
+ join(rootPath, 'config/serverless.config.yml'),
115
+ 'main_config:\n iz_serviceTag: CategoryTreeStandard\n'
116
+ );
117
+ await writeFile(
118
+ join(rootPath, 'app/src/schemas/ErrorSchemas/errorgraph.js'),
119
+ `export default [
120
+ {
121
+ errorTag: 'NOT_FOUND_SCHEMA',
122
+ errorAction: 'throw',
123
+ sharedLibrary: true
124
+ },
125
+ {
126
+ errorTag: 'INVALID_GENERATED_BY',
127
+ errorAction: 'throw',
128
+ sharedLibrary: true
129
+ },
130
+ {
131
+ errorTag: 'MISSING_USER_NODE',
132
+ errorAction: 'throw',
133
+ sharedLibrary: true
134
+ }
135
+ ];
136
+ `
137
+ );
138
+
139
+ // Run generate
140
+ await generate(rootPath);
141
+
142
+ const targetFile = join(
143
+ rootPath,
144
+ 'app/src/schemas/ErrorSchemas/generateCode/errorSchema.js'
145
+ );
146
+ const content = await readFile(targetFile, 'utf8');
147
+
148
+ assert.match(content, /export const ERROR_TYPES = {/);
149
+ assert.match(content, /NOT_FOUND_SCHEMA:/);
150
+ assert.match(content, /INVALID_GENERATED_BY:/);
151
+ assert.match(content, /MISSING_USER_NODE:/);
152
+ assert.match(content, /serviceTag:\s*'CategoryTreeStandard'/);
153
+ assert.doesNotMatch(content, /export default/);
154
+ });
@@ -70,7 +70,7 @@ test('generate logs its lifecycle through console.log', async () => {
70
70
 
71
71
  assert.match(messages[0], /STATUS=STARTED/);
72
72
  assert.ok(messages.some((message) => {
73
- return /STATUS=SCHEMAS_LOADED/.test(message);
73
+ return /STATUS=SCHEMAS_LOADED.*notifications=/.test(message);
74
74
  }));
75
75
  assert.ok(messages.some((message) => {
76
76
  return /STATUS=ARTIFACTS_EMITTED/.test(message);