@izara_project/izara-core-generate-service-code 1.0.91 → 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.
Files changed (36) hide show
  1. package/package.json +7 -10
  2. package/src/compile/flowValidator.js +6 -0
  3. package/src/compile/loadSchemas.js +195 -69
  4. package/src/emit/coreFunction/templates/UploadSchema_Main.ejs +2 -2
  5. package/src/emit/crud/generateCrudApplicationArtifacts.js +15 -17
  6. package/src/emit/crud/generateDynamicCrudFlowSchemas.js +16 -21
  7. package/src/emit/crud/loadLocalSchemaArrays.js +4 -8
  8. package/src/emit/custom/customFlowPaths.js +149 -0
  9. package/src/emit/custom/customFlowS3.js +156 -0
  10. package/src/emit/custom/customFlowSteps.js +385 -0
  11. package/src/emit/custom/customFlowTemplates.js +28 -0
  12. package/src/emit/custom/customFlows.js +48 -19
  13. package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +57 -692
  14. package/src/emit/custom/generateCustomFlowApplicationArtifacts.js +27 -25
  15. package/src/emit/error/emitErrorSchemas.js +73 -0
  16. package/src/emit/error/templates/errorSchema.ejs +8 -0
  17. package/src/emit/rbac/generateCreateTargetRoleHandler.js +11 -13
  18. package/src/emit/rbac/generateDynamicRbacFlowSchemas.js +14 -18
  19. package/src/emit/relationship/generateDynamicRelationshipFlowSchemas.js +16 -19
  20. package/src/emit/relationship/generateRelationshipApplicationArtifacts.js +19 -19
  21. package/src/emit/resource/dynamoResourceArtifacts.js +18 -0
  22. package/src/emit/resource/emitResourceArtifacts.js +13 -1247
  23. package/src/emit/resource/generateResourceArtifacts.js +20 -20
  24. package/src/emit/resource/iamResourceArtifacts.js +612 -0
  25. package/src/emit/resource/resourceState.js +564 -0
  26. package/src/emit/resource/resourceTemplate.js +30 -0
  27. package/src/emit/resource/s3ResourceArtifacts.js +14 -0
  28. package/src/emit/resource/sqsResourceArtifacts.js +113 -0
  29. package/src/emit/shared/runSubsystemGeneration.js +55 -0
  30. package/src/emit/system/emitSystemWebSocketArtifacts.js +2 -1
  31. package/src/generate.js +37 -4
  32. package/test/crudApplicationArtifacts.test.js +72 -0
  33. package/test/customFlowApplicationArtifacts.test.js +98 -0
  34. package/test/errorSchemas.test.js +154 -0
  35. package/test/publicApi.test.js +1 -1
  36. package/test/resourceArtifacts.test.js +99 -0
@@ -0,0 +1,113 @@
1
+ import { indentYaml, renderResourceTemplate } from './resourceTemplate.js';
2
+
3
+ export async function renderResourceGroup({
4
+ ownTopics = [],
5
+ sqsQueues = [],
6
+ dsqQueues = [],
7
+ webSocketSubscriptions = [],
8
+ s3Subscriptions = []
9
+ }) {
10
+ const hasResources =
11
+ ownTopics.length > 0 ||
12
+ sqsQueues.length > 0 ||
13
+ dsqQueues.length > 0 ||
14
+ webSocketSubscriptions.length > 0 ||
15
+ s3Subscriptions.length > 0;
16
+ if (!hasResources) {
17
+ return '';
18
+ }
19
+
20
+ const chunks = await Promise.all([
21
+ ...ownTopics.map((queueName) =>
22
+ renderResourceTemplate('ResourceYaml.ejs', { queueName })
23
+ ),
24
+ ...sqsQueues.map((queueName) =>
25
+ renderResourceTemplate('ResourceSqsYaml.ejs', { queueName })
26
+ ),
27
+ ...dsqQueues.map((queueName) =>
28
+ renderResourceTemplate('ResourceSqsYaml.ejs', { queueName })
29
+ )
30
+ ]);
31
+ const wsSubs = await Promise.all(
32
+ webSocketSubscriptions.map((topicName) =>
33
+ renderWebSocketSubscription(topicName)
34
+ )
35
+ );
36
+ const s3Subs = await Promise.all(
37
+ s3Subscriptions.map(({ subscriptionName, topicName, queueName }) =>
38
+ renderResourceTemplate('S3Subscription_Yaml.ejs', {
39
+ subscriptionName,
40
+ topicName,
41
+ queueName
42
+ })
43
+ )
44
+ );
45
+ const parts = [chunks.join('\n')];
46
+ if (wsSubs.length > 0) {
47
+ parts.push(wsSubs.join('\n'));
48
+ }
49
+ if (s3Subs.length > 0) {
50
+ parts.push(s3Subs.join('\n'));
51
+ }
52
+ const body = parts.filter(Boolean).join('\n');
53
+ if (!body.trim()) {
54
+ return '';
55
+ }
56
+ return `Resources:\n${indentYaml(body, 2)}`;
57
+ }
58
+
59
+ export async function renderCrudInSqs(state) {
60
+ return renderResourceGroup({
61
+ ownTopics: state.crudOwnTopics,
62
+ sqsQueues: state.crudSqsQueues
63
+ });
64
+ }
65
+
66
+ export async function renderRelationshipInSqs(state) {
67
+ return renderResourceGroup({
68
+ ownTopics: state.relationshipOwnTopics,
69
+ sqsQueues: state.relationshipSqsQueues,
70
+ dsqQueues: state.relationshipDsqQueues
71
+ });
72
+ }
73
+
74
+ export async function renderRbacInSqs(state) {
75
+ return renderResourceGroup({
76
+ ownTopics: state.rbacOwnTopics,
77
+ sqsQueues: state.rbacSqsQueues
78
+ });
79
+ }
80
+
81
+ export async function renderCustomFlowInSqs(state) {
82
+ return renderResourceGroup({
83
+ ownTopics: state.customOwnTopics,
84
+ sqsQueues: state.customSqsQueues,
85
+ dsqQueues: state.customDsqQueues,
86
+ s3Subscriptions: state.s3Subscriptions
87
+ });
88
+ }
89
+
90
+ export async function renderSystemInSqs(state) {
91
+ return renderResourceGroup({
92
+ ownTopics: state.systemOwnTopics,
93
+ sqsQueues: state.systemSqsQueues,
94
+ dsqQueues: state.systemDsqQueues,
95
+ webSocketSubscriptions: state.webSocketSubscriptions
96
+ });
97
+ }
98
+
99
+ export async function renderOut(state) {
100
+ const renderedTopics = await Promise.all(
101
+ state.outTopics.map((topicName) =>
102
+ renderResourceTemplate('FlowOut_Yaml.ejs', { topicName })
103
+ )
104
+ );
105
+ const body = renderedTopics.join('\n');
106
+ return `Resources:\n${indentYaml(body, 2)}`;
107
+ }
108
+
109
+ export function renderWebSocketSubscription(topicName) {
110
+ return renderResourceTemplate('WebSocketSubscription_Yaml.ejs', {
111
+ topicName
112
+ });
113
+ }
@@ -0,0 +1,55 @@
1
+ import { compileProject } from '../../compile/compileProject.js';
2
+ import { buildGenerationPlan } from '../../plan/buildGenerationPlan.js';
3
+ import { commitArtifacts } from '../../output/commitArtifacts.js';
4
+
5
+ /**
6
+ * Common pipeline runner for emitting and committing subsystem artifacts.
7
+ *
8
+ * @param {Object} params
9
+ * @param {string} params.rootPath Root path of the service project.
10
+ * @param {Object} [params.options={}] Options passed to compile and load.
11
+ * @param {Function} [params.loadInputs] Function to load schemas or inputs.
12
+ * @param {Function} params.emitArtifacts Function to emit artifacts.
13
+ * @param {string} params.managedScope Scope identifier for generation plan.
14
+ * @param {Array<string>|Function} params.resolveManagedPaths Managed paths.
15
+ * @returns {Promise<{ project: Object, files: Array<string> }>}
16
+ */
17
+ export async function runSubsystemGeneration({
18
+ rootPath,
19
+ options = {},
20
+ loadInputs,
21
+ emitArtifacts,
22
+ managedScope,
23
+ resolveManagedPaths
24
+ }) {
25
+ const project = compileProject({ ...options, rootPath });
26
+
27
+ let inputs = {};
28
+ if (typeof loadInputs === 'function') {
29
+ inputs = await loadInputs(project, options);
30
+ }
31
+
32
+ const artifacts = await emitArtifacts({
33
+ project,
34
+ options,
35
+ ...inputs
36
+ });
37
+
38
+ let managedPaths = resolveManagedPaths;
39
+ if (typeof resolveManagedPaths === 'function') {
40
+ managedPaths = await resolveManagedPaths({
41
+ project,
42
+ options,
43
+ artifacts,
44
+ ...inputs
45
+ });
46
+ }
47
+
48
+ const plan = buildGenerationPlan(project, artifacts, {
49
+ managedPaths,
50
+ managedScope
51
+ });
52
+ const files = await commitArtifacts(plan);
53
+
54
+ return { project, files };
55
+ }
@@ -1,5 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import ejsLib from 'ejs';
3
+ import { flowHasOwnTopic } from '../custom/customFlows.js';
3
4
 
4
5
  const TEMPLATE_URL = new URL('./templates/', import.meta.url);
5
6
  const SOURCE = 'app/src/generatedCode/SystemFlowSchemas/WebSocketMain/source';
@@ -126,7 +127,7 @@ export function hasSystemWebSocket(
126
127
  Array.isArray(flow?.event) &&
127
128
  (flow.event.includes('s3') ||
128
129
  (includeRelationshipOwnTopic &&
129
- flow.event.includes('ownTopic') &&
130
+ flowHasOwnTopic(flow) &&
130
131
  (flow.initiateBy === 'user' || flow.initiateBy === 'systemWithUser')))
131
132
  )
132
133
  );
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
  }
@@ -1388,6 +1388,78 @@ test('emits autoProvisionOwnerTargetRole in CreateNodeCallback when service has
1388
1388
  );
1389
1389
  });
1390
1390
 
1391
+ test('emits synchronous before-hook invocation in Create_Main when event is generateCode and invocationType is lambdaSyncInv', async () => {
1392
+ const rootPath = await createProject(true);
1393
+ const schemas = join(rootPath, 'app/src/schemas');
1394
+ await writeFile(
1395
+ join(schemas, 'FlowSchemas/ValidateBeforeCreateSync.js'),
1396
+ "export default { flowTag: 'validateBeforeCreateSync', event: ['generateCode'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'before', invocationType: 'lambdaSyncInv' }, objType: { objectType: 'order' } };\n"
1397
+ );
1398
+ await generateCrudApplicationArtifacts(rootPath, {
1399
+ serviceTag: 'orders'
1400
+ });
1401
+
1402
+ const createMain = await readFile(
1403
+ join(
1404
+ rootPath,
1405
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
1406
+ ),
1407
+ 'utf8'
1408
+ );
1409
+ assert.match(
1410
+ createMain,
1411
+ /lambda\.invokeSync\(\s*_izContext,\s*await lambdaSharedLib\.lambdaFunctionName\(\s*_izContext,\s*'validateBeforeCreateSync'\s*\)/
1412
+ );
1413
+ assert.match(createMain, /Hook validateBeforeCreateSync failed/);
1414
+
1415
+ execFileSync(
1416
+ process.execPath,
1417
+ [
1418
+ '--check',
1419
+ join(
1420
+ rootPath,
1421
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
1422
+ )
1423
+ ],
1424
+ { stdio: 'inherit' }
1425
+ );
1426
+ });
1427
+
1428
+ test('emits async before-hook callback and SNS publish in Create_Main when event is generateCode and invocationType is ownTopic', async () => {
1429
+ const rootPath = await createProject(true);
1430
+ const schemas = join(rootPath, 'app/src/schemas');
1431
+ await writeFile(
1432
+ join(schemas, 'FlowSchemas/ValidateBeforeCreateAsync.js'),
1433
+ "export default { flowTag: 'validateBeforeCreateAsync', event: ['generateCode'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'before', invocationType: 'ownTopic' }, objType: { objectType: 'order' } };\n"
1434
+ );
1435
+ await generateCrudApplicationArtifacts(rootPath, {
1436
+ serviceTag: 'orders'
1437
+ });
1438
+
1439
+ const createMain = await readFile(
1440
+ join(
1441
+ rootPath,
1442
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
1443
+ ),
1444
+ 'utf8'
1445
+ );
1446
+ assert.match(createMain, /sns\.publishAsync\(_izContext,/);
1447
+ assert.match(
1448
+ createMain,
1449
+ /snsTopicArnByFlowSchema\(\s*_izContext,\s*'validateBeforeCreateAsync_In'\s*\)/
1450
+ );
1451
+ assert.match(createMain, /CreateBeforeHookCallbackHdrSqs/);
1452
+
1453
+ const callbackHandler = await readFile(
1454
+ join(
1455
+ rootPath,
1456
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateBeforeHookCallback_HdrSqs.js'
1457
+ ),
1458
+ 'utf8'
1459
+ );
1460
+ assert.match(callbackHandler, /CreateBeforeHookCallback_Main/);
1461
+ });
1462
+
1391
1463
  async function createProject(withCrudObject) {
1392
1464
  const rootPath = await mkdtemp(
1393
1465
  join(tmpdir(), 'generate-service-code-v2-crud-application-')
@@ -1173,3 +1173,101 @@ test('renders previousSteps extTopic subscriptions in InitialSetupConfig', async
1173
1173
  /sqsEndpoint: 'OrderBuildOrderSellOfferLinkAndReserveOrderDataHdrSqs'/
1174
1174
  );
1175
1175
  });
1176
+
1177
+ test('emits handlers, YAML and templates for flow with event generateCode and invocationType ownTopic', async () => {
1178
+ const rootPath = await mkdtemp(
1179
+ join(tmpdir(), 'generate-service-code-v2-custom-gen-own-')
1180
+ );
1181
+ roots.add(rootPath);
1182
+ const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
1183
+ await mkdir(schemaRoot, { recursive: true });
1184
+ await writeFile(
1185
+ join(schemaRoot, 'GenOwn.js'),
1186
+ `export default {
1187
+ flowTag: 'genOwn',
1188
+ event: ['generateCode'],
1189
+ generatedCodeConfig: {
1190
+ generateCodeTag: 'create',
1191
+ codeHookTag: 'before',
1192
+ invocationType: 'ownTopic'
1193
+ },
1194
+ flowSteps: [{ stepName: 'Start', properties: ['dummyId'] }],
1195
+ stepProperties: { dummyId: { type: 'string' } }
1196
+ };\n`
1197
+ );
1198
+
1199
+ await generateCustomFlowApplicationArtifacts(rootPath);
1200
+
1201
+ const mainPath = join(
1202
+ rootPath,
1203
+ 'app/src/generatedCode/FlowSchemas/GenOwn/source/GenOwn_Main.js'
1204
+ );
1205
+ const hdrSqsPath = join(
1206
+ rootPath,
1207
+ 'app/src/generatedCode/FlowSchemas/GenOwn/source/GenOwn_HdrSqs.js'
1208
+ );
1209
+ const wbsMainPath = join(
1210
+ rootPath,
1211
+ 'app/src/generatedCode/FlowSchemas/GenOwn/source/GenOwn_WbsMain.js'
1212
+ );
1213
+ const hdrWbsPath = join(
1214
+ rootPath,
1215
+ 'app/src/generatedCode/FlowSchemas/GenOwn/source/GenOwn_HdrWbs.js'
1216
+ );
1217
+
1218
+ assert.match(await readFile(mainPath, 'utf8'), /asyncFlowSharedLib/);
1219
+ assert.match(await readFile(hdrSqsPath, 'utf8'), /GenOwn_Main/);
1220
+ assert.match(await readFile(wbsMainPath, 'utf8'), /GenOwn_WbsMain/);
1221
+ assert.match(await readFile(hdrWbsPath, 'utf8'), /GenOwn_WbsMain/);
1222
+
1223
+ const yaml = await readFile(
1224
+ join(rootPath, 'app/sls_yaml/generatedCode/source/flow-schema.yml'),
1225
+ 'utf8'
1226
+ );
1227
+ assert.match(yaml, /GenOwnHdrWbs:/);
1228
+ assert.match(yaml, /GenOwnHdrSqs:/);
1229
+ });
1230
+
1231
+ test('emits handler and YAML for flow with event generateCode and invocationType lambdaSyncInv', async () => {
1232
+ const rootPath = await mkdtemp(
1233
+ join(tmpdir(), 'generate-service-code-v2-custom-gen-sync-')
1234
+ );
1235
+ roots.add(rootPath);
1236
+ const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
1237
+ await mkdir(schemaRoot, { recursive: true });
1238
+ await writeFile(
1239
+ join(schemaRoot, 'GenSync.js'),
1240
+ `export default {
1241
+ flowTag: 'genSync',
1242
+ event: ['generateCode'],
1243
+ generatedCodeConfig: {
1244
+ generateCodeTag: 'create',
1245
+ codeHookTag: 'before',
1246
+ invocationType: 'lambdaSyncInv'
1247
+ },
1248
+ flowSteps: [{ stepName: 'Start', properties: ['dummyId'] }],
1249
+ stepProperties: { dummyId: { type: 'string' } }
1250
+ };\n`
1251
+ );
1252
+
1253
+ await generateCustomFlowApplicationArtifacts(rootPath);
1254
+
1255
+ const mainPath = join(
1256
+ rootPath,
1257
+ 'app/src/generatedCode/FlowSchemas/GenSync/source/GenSync_Main.js'
1258
+ );
1259
+ const hdrInvPath = join(
1260
+ rootPath,
1261
+ 'app/src/generatedCode/FlowSchemas/GenSync/source/GenSync_HdrInv.js'
1262
+ );
1263
+
1264
+ assert.match(await readFile(mainPath, 'utf8'), /export default async function GenSync/);
1265
+ assert.match(await readFile(hdrInvPath, 'utf8'), /GenSync_Main/);
1266
+
1267
+ const yaml = await readFile(
1268
+ join(rootPath, 'app/sls_yaml/generatedCode/source/flow-schema.yml'),
1269
+ 'utf8'
1270
+ );
1271
+ assert.match(yaml, /GenSyncHdrInv:/);
1272
+ });
1273
+
@@ -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);