@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 +1 -1
- package/src/compile/loadSchemas.js +18 -0
- package/src/emit/coreFunction/templates/UploadSchema_Main.ejs +2 -2
- package/src/emit/error/emitErrorSchemas.js +73 -0
- package/src/emit/error/templates/errorSchema.ejs +8 -0
- package/src/generate.js +37 -4
- package/test/errorSchemas.test.js +154 -0
- package/test/publicApi.test.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@izara_project/izara-core-generate-service-code",
|
|
3
|
-
"version": "1.0.
|
|
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
|
|
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
|
|
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 [
|
|
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
|
+
});
|
package/test/publicApi.test.js
CHANGED
|
@@ -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
|
|
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);
|