@hamak/smart-data-dico 1.0.4 → 1.1.1

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 (49) hide show
  1. package/backend/dist/server.mjs +82213 -0
  2. package/bin/cli.js +28 -17
  3. package/package.json +28 -27
  4. package/backend/package.json +0 -51
  5. package/backend/src/__tests__/integration/api.test.ts +0 -149
  6. package/backend/src/__tests__/setup.ts +0 -24
  7. package/backend/src/__tests__/utils/testUtils.ts +0 -76
  8. package/backend/src/adapters/EntityFileAdapter.ts +0 -154
  9. package/backend/src/adapters/YamlFileInfoEnricher.ts +0 -52
  10. package/backend/src/controllers/authController.ts +0 -131
  11. package/backend/src/controllers/diagramController.ts +0 -143
  12. package/backend/src/controllers/dictionaryController.ts +0 -306
  13. package/backend/src/controllers/importExportController.ts +0 -64
  14. package/backend/src/controllers/perspectiveController.ts +0 -90
  15. package/backend/src/controllers/serviceController.ts +0 -418
  16. package/backend/src/controllers/stereotypeController.ts +0 -59
  17. package/backend/src/controllers/versionController.ts +0 -226
  18. package/backend/src/kernel/config.ts +0 -43
  19. package/backend/src/middleware/auth.ts +0 -128
  20. package/backend/src/middleware/jwtAuth.ts +0 -100
  21. package/backend/src/models/Dictionary.ts +0 -38
  22. package/backend/src/models/EntitySchema.ts +0 -393
  23. package/backend/src/models/__tests__/Dictionary.test.ts +0 -92
  24. package/backend/src/models/__tests__/EntitySchema.test.ts +0 -119
  25. package/backend/src/routes/index.ts +0 -120
  26. package/backend/src/scripts/migrate-to-uuid.ts +0 -24
  27. package/backend/src/server.ts +0 -158
  28. package/backend/src/services/__mocks__/entityService.ts +0 -38
  29. package/backend/src/services/__mocks__/serviceService.ts +0 -88
  30. package/backend/src/services/__mocks__/versionService.ts +0 -38
  31. package/backend/src/services/__tests__/dictionaryService.test.ts +0 -74
  32. package/backend/src/services/diagramService.ts +0 -165
  33. package/backend/src/services/dictionaryService.ts +0 -582
  34. package/backend/src/services/entityService.ts +0 -102
  35. package/backend/src/services/exportService.ts +0 -172
  36. package/backend/src/services/importService.ts +0 -208
  37. package/backend/src/services/perspectiveService.ts +0 -276
  38. package/backend/src/services/qualityService.ts +0 -121
  39. package/backend/src/services/serviceService.ts +0 -763
  40. package/backend/src/services/stereotypeService.ts +0 -98
  41. package/backend/src/services/versionService.ts +0 -135
  42. package/backend/src/setupTests.ts +0 -12
  43. package/backend/src/utils/__mocks__/fileOperations.ts +0 -116
  44. package/backend/src/utils/fileOperations.ts +0 -602
  45. package/backend/src/utils/logger.ts +0 -38
  46. package/backend/src/utils/migration.ts +0 -254
  47. package/backend/src/utils/swagger.ts +0 -358
  48. package/backend/src/utils/uuid.ts +0 -41
  49. package/backend/tsconfig.json +0 -20
@@ -1,582 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import YAML from 'yaml';
4
-
5
- import { Dictionary, Package } from '../models/Dictionary.js';
6
- import { Entity, Relationship } from '../models/EntitySchema.js';
7
- import { ensureDirectoryStructure, listAllDictionaries, listAllEntities, listMicroserviceEntities, listMicroservices, readEntityFile, readRelationshipsFile, writeDictionaryMetadata } from '../utils/fileOperations.js';
8
- import { logger } from '../utils/logger.js';
9
-
10
- // Base directory for data dictionaries - use the same path as in fileOperations.ts
11
- const DATA_DICTIONARIES_DIR = path.join(process.cwd(), '..', 'data-dictionaries');
12
-
13
- /**
14
- * Dictionary Service
15
- * Provides functionality for managing data dictionaries
16
- */
17
- export class DictionaryService {
18
-
19
- /**
20
- * Create a new package (subpackage) at the given path.
21
- */
22
- private validatePackageName(name: string): string | null {
23
- if (!name) return 'Package name is required';
24
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) return 'Package name must be kebab-case (lowercase letters, numbers, hyphens)';
25
- return null;
26
- }
27
-
28
- public async createPackageAtPath(rootPackageName: string, packagePath: string[], packageData: Partial<Package>): Promise<{ success: boolean; errors?: string[]; package?: Package }> {
29
- try {
30
- // Validate package name
31
- const nameToValidate = packagePath.length > 0 ? packagePath[packagePath.length - 1] : rootPackageName;
32
- const nameError = this.validatePackageName(nameToValidate);
33
- if (nameError) return { success: false, errors: [nameError] };
34
-
35
- const baseDir = path.join(DATA_DICTIONARIES_DIR, 'microservices', rootPackageName, ...packagePath);
36
- if (!fs.existsSync(baseDir)) {
37
- fs.mkdirSync(baseDir, { recursive: true });
38
- } else {
39
- return { success: false, errors: ['Package directory already exists'] };
40
- }
41
- const metaPath = path.join(baseDir, 'metadata.yaml');
42
- const metaContent = YAML.stringify({
43
- id: packageData.id || packagePath[packagePath.length - 1],
44
- name: packageData.name || packagePath[packagePath.length - 1],
45
- description: packageData.description,
46
- type: packageData.type,
47
- metadata: packageData.metadata || []
48
- });
49
- fs.writeFileSync(metaPath, metaContent, 'utf8');
50
- return {
51
- success: true,
52
- package: {
53
- id: packageData.id || packagePath[packagePath.length - 1],
54
- name: packageData.name || packagePath[packagePath.length - 1],
55
- description: packageData.description,
56
- type: packageData.type,
57
- entities: [],
58
- subPackages: [],
59
- relationships: [],
60
- metadata: packageData.metadata || []
61
- }
62
- };
63
- } catch (error: any) {
64
- logger.error('Error creating package at path', error);
65
- return { success: false, errors: [error.message || String(error)] };
66
- }
67
- }
68
-
69
- /**
70
- * Update a package's metadata at the given path.
71
- */
72
- public async updatePackageAtPath(rootPackageName: string, packagePath: string[], packageData: Partial<Package>): Promise<{ success: boolean; errors?: string[]; package?: Package }> {
73
- try {
74
- const baseDir = path.join(DATA_DICTIONARIES_DIR, 'microservices', rootPackageName, ...packagePath);
75
- if (!fs.existsSync(baseDir)) {
76
- return { success: false, errors: ['Package directory does not exist'] };
77
- }
78
- const metaPath = path.join(baseDir, 'metadata.yaml');
79
- if (!fs.existsSync(metaPath)) {
80
- return { success: false, errors: ['metadata.yaml does not exist'] };
81
- }
82
- const oldMeta = YAML.parse(fs.readFileSync(metaPath, 'utf8')) || {};
83
- const newMeta = {
84
- ...oldMeta,
85
- ...packageData,
86
- id: packageData.id || oldMeta.id,
87
- name: packageData.name || oldMeta.name,
88
- description: packageData.description ?? oldMeta.description,
89
- type: packageData.type ?? oldMeta.type,
90
- metadata: packageData.metadata ?? oldMeta.metadata
91
- };
92
- fs.writeFileSync(metaPath, YAML.stringify(newMeta), 'utf8');
93
- return {
94
- success: true,
95
- package: {
96
- id: newMeta.id,
97
- name: newMeta.name,
98
- description: newMeta.description,
99
- type: newMeta.type,
100
- entities: [],
101
- subPackages: [],
102
- relationships: [],
103
- metadata: newMeta.metadata
104
- }
105
- };
106
- } catch (error: any) {
107
- logger.error('Error updating package at path', error);
108
- return { success: false, errors: [error.message || String(error)] };
109
- }
110
- }
111
-
112
- /**
113
- * Delete a package (and all its contents) at the given path.
114
- */
115
- public async deletePackageAtPath(rootPackageName: string, packagePath: string[], force = false): Promise<{ success: boolean; errors?: string[] }> {
116
- try {
117
- const baseDir = path.join(DATA_DICTIONARIES_DIR, 'microservices', rootPackageName, ...packagePath);
118
- if (!fs.existsSync(baseDir)) {
119
- return { success: false, errors: ['Package directory does not exist'] };
120
- }
121
-
122
- // Non-empty check unless force=true
123
- if (!force) {
124
- const entries = fs.readdirSync(baseDir);
125
- const hasEntities = entries.some(e => e.endsWith('.yaml') && e !== 'metadata.yaml' && e !== 'relationships.yaml');
126
- const hasSubPackages = entries.some(e => fs.statSync(path.join(baseDir, e)).isDirectory());
127
- if (hasEntities || hasSubPackages) {
128
- return { success: false, errors: ['Package is not empty. Delete its entities and sub-packages first, or use force=true.'] };
129
- }
130
- }
131
-
132
- fs.rmSync(baseDir, { recursive: true, force: true });
133
- return { success: true };
134
- } catch (error: any) {
135
- logger.error('Error deleting package at path', error);
136
- return { success: false, errors: [error.message || String(error)] };
137
- }
138
- }
139
-
140
- /**
141
- * Recursively builds a Package hierarchy from a directory.
142
- * Now also reads relationships.yaml at each package level.
143
- */
144
- private async buildPackageHierarchy(dirPath: string, packageName: string): Promise<Package> {
145
- if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
146
- return {
147
- id: packageName,
148
- name: packageName,
149
- description: undefined,
150
- type: undefined,
151
- entities: [],
152
- subPackages: [],
153
- relationships: [],
154
- metadata: undefined
155
- };
156
- }
157
-
158
- // Read package metadata if present
159
- let packageMeta: Partial<Package> = {};
160
- const metaPath = path.join(dirPath, 'metadata.yaml');
161
- if (fs.existsSync(metaPath)) {
162
- const metaContent = fs.readFileSync(metaPath, 'utf8');
163
- packageMeta = YAML.parse(metaContent) as Partial<Package>;
164
- }
165
-
166
- // Read relationships from package-level file
167
- const relationships = await readRelationshipsFile(dirPath);
168
-
169
- // List all files and subdirectories
170
- const entries = fs.readdirSync(dirPath, { withFileTypes: true });
171
- const entities: Entity[] = [];
172
- const subPackages: Package[] = [];
173
-
174
- for (const entry of entries) {
175
- const entryPath = path.join(dirPath, entry.name);
176
- if (entry.isDirectory()) {
177
- const subpkg = await this.buildPackageHierarchy(entryPath, entry.name);
178
- subPackages.push(subpkg);
179
- } else if (
180
- entry.isFile() &&
181
- entry.name.endsWith('.yaml') &&
182
- entry.name !== 'metadata.yaml' &&
183
- entry.name !== 'relationships.yaml'
184
- ) {
185
- try {
186
- const fileContent = fs.readFileSync(entryPath, 'utf8');
187
- const entity = YAML.parse(fileContent) as Entity;
188
- entities.push(entity);
189
- } catch (e) {
190
- logger.warn(`Failed to parse entity YAML: ${entryPath}: ${e}`);
191
- }
192
- }
193
- }
194
-
195
- return {
196
- id: packageMeta.id || packageName,
197
- name: packageMeta.name || packageName,
198
- description: packageMeta.description,
199
- type: packageMeta.type,
200
- entities,
201
- subPackages,
202
- relationships,
203
- metadata: packageMeta.metadata
204
- };
205
- }
206
-
207
- // --- IMPLEMENTED METHODS ---
208
-
209
- public async getAllDictionaries(): Promise<Dictionary[]> {
210
- try {
211
- const dictionaryIds = await listAllDictionaries();
212
- const dictionaries: Dictionary[] = [];
213
-
214
- for (const id of dictionaryIds) {
215
- const dictionary = await this.getDictionaryById(id);
216
- if (dictionary) {
217
- dictionaries.push(dictionary);
218
- }
219
- }
220
-
221
- return dictionaries;
222
- } catch (error) {
223
- logger.error('Error getting all dictionaries', error);
224
- return [];
225
- }
226
- }
227
-
228
- public async getDictionaryById(id: string): Promise<Dictionary | null> {
229
- try {
230
- if (id.includes('microservices')) {
231
- const rootPackage: Package = {
232
- id: id,
233
- name: id.split('/').pop() || id,
234
- description: `Microservice: ${id}`,
235
- entities: [],
236
- subPackages: [],
237
- relationships: []
238
- };
239
-
240
- return {
241
- id,
242
- name: id.split('/').pop() || id,
243
- description: `Microservice: ${id}`,
244
- createdAt: new Date(),
245
- updatedAt: new Date(),
246
- rootPackage
247
- };
248
- }
249
-
250
- const metadataPath = path.join(DATA_DICTIONARIES_DIR, id, 'metadata.yaml');
251
-
252
- if (!fs.existsSync(metadataPath)) {
253
- return null;
254
- }
255
-
256
- const metadataContent = fs.readFileSync(metadataPath, 'utf8');
257
- const metadata = YAML.parse(metadataContent) as Dictionary;
258
-
259
- return metadata;
260
- } catch (error) {
261
- logger.error(`Error getting dictionary by ID: ${id}`, error);
262
- return null;
263
- }
264
- }
265
-
266
- public async getDictionaryEntries(id: string): Promise<any[]> {
267
- try {
268
- if (id.startsWith('microservices/')) {
269
- const microservice = id.replace('microservices/', '');
270
- const entityNames = await listMicroserviceEntities(microservice);
271
- const entries: any[] = [];
272
-
273
- for (const entityName of entityNames) {
274
- const entity = await readEntityFile(microservice, entityName);
275
- if (entity) {
276
- for (const attr of entity.attributes || []) {
277
- entries.push({
278
- id: `${entity.uuid || ''}_${attr.name}`,
279
- name: attr.name,
280
- description: attr.description || '',
281
- type: attr.type || 'string',
282
- format: attr.constraints?.format,
283
- required: attr.required || false
284
- });
285
- }
286
- }
287
- }
288
-
289
- return entries;
290
- }
291
-
292
- return [];
293
- } catch (error) {
294
- logger.error(`Error getting dictionary entries: ${id}`, error);
295
- return [];
296
- }
297
- }
298
-
299
- public async getEntityAttributes(microservice: string, entityName: string): Promise<any[]> {
300
- try {
301
- const entity = await readEntityFile(microservice, entityName);
302
-
303
- if (!entity || !entity.attributes) {
304
- return [];
305
- }
306
-
307
- return entity.attributes.map((attr: any) => ({
308
- id: `${entity.uuid || ''}_${attr.name}`,
309
- name: attr.name,
310
- description: attr.description || '',
311
- type: attr.type || 'string',
312
- format: attr.constraints?.format,
313
- required: attr.required || false
314
- }));
315
- } catch (error) {
316
- logger.error(`Error getting entity attributes: ${microservice}.${entityName}`, error);
317
- return [];
318
- }
319
- }
320
-
321
- public async createDictionary(dictionaryData: Dictionary): Promise<Dictionary | { error: string; code: string }> {
322
- try {
323
- if (!dictionaryData.name) {
324
- return { error: 'Dictionary name is required', code: 'MISSING_NAME' };
325
- }
326
-
327
- if (!dictionaryData.id) {
328
- dictionaryData.id = dictionaryData.name.toLowerCase().replace(/\s+/g, '-');
329
- }
330
-
331
- const existingDictionaries = await listAllDictionaries();
332
- if (existingDictionaries.includes(dictionaryData.id)) {
333
- return { error: `Dictionary with ID ${dictionaryData.id} already exists`, code: 'DUPLICATE_NAME' };
334
- }
335
-
336
- dictionaryData.createdAt = new Date();
337
- dictionaryData.updatedAt = new Date();
338
-
339
- const success = await writeDictionaryMetadata(dictionaryData);
340
-
341
- if (!success) {
342
- return { error: 'Failed to write dictionary metadata', code: 'WRITE_ERROR' };
343
- }
344
-
345
- return dictionaryData;
346
- } catch (error) {
347
- logger.error('Error creating dictionary', error);
348
- return { error: 'Internal server error', code: 'INTERNAL_ERROR' };
349
- }
350
- }
351
-
352
- public async getPackageHierarchy(rootPackage: string): Promise<Package | null> {
353
- try {
354
- const dirPath = path.join(DATA_DICTIONARIES_DIR, 'microservices', rootPackage);
355
- if (!fs.existsSync(dirPath)) {
356
- return null;
357
- }
358
- return await this.buildPackageHierarchy(dirPath, rootPackage);
359
- } catch (error) {
360
- logger.error('Error in getPackageHierarchy', error);
361
- return null;
362
- }
363
- }
364
-
365
- public async getTabularData(rootPackage: string): Promise<any[]> {
366
- try {
367
- const hierarchy = await this.getPackageHierarchy(rootPackage);
368
- if (!hierarchy) {
369
- return [];
370
- }
371
-
372
- const result: any[] = [];
373
- this.flattenHierarchy(hierarchy, result, '');
374
-
375
- return result;
376
- } catch (error) {
377
- logger.error(`Error getting tabular data: ${rootPackage}`, error);
378
- return [];
379
- }
380
- }
381
-
382
- private flattenHierarchy(pkg: Package, result: any[], parentPath: string): void {
383
- const currentPath = parentPath ? `${parentPath}/${pkg.name}` : pkg.name;
384
-
385
- result.push({
386
- type: 'package',
387
- id: pkg.id,
388
- name: pkg.name,
389
- description: pkg.description,
390
- path: currentPath,
391
- level: parentPath.split('/').length
392
- });
393
-
394
- for (const entity of pkg.entities || []) {
395
- result.push({
396
- type: 'entity',
397
- id: entity.uuid,
398
- name: entity.name,
399
- description: entity.description,
400
- path: `${currentPath}/${entity.name}`,
401
- level: parentPath.split('/').length + 1
402
- });
403
- }
404
-
405
- for (const subpkg of pkg.subPackages || []) {
406
- this.flattenHierarchy(subpkg, result, currentPath);
407
- }
408
- }
409
-
410
- public async getPackageByPath(rootPackage: string, packagePath: string[]): Promise<Package | null> {
411
- try {
412
- const rootHierarchy = await this.getPackageHierarchy(rootPackage);
413
- if (!rootHierarchy) {
414
- return null;
415
- }
416
-
417
- if (!packagePath.length) {
418
- return rootHierarchy;
419
- }
420
-
421
- let currentPackage = rootHierarchy;
422
-
423
- for (const segment of packagePath) {
424
- const subPackage = currentPackage.subPackages?.find(p => p.name === segment);
425
-
426
- if (!subPackage) {
427
- return null;
428
- }
429
-
430
- currentPackage = subPackage;
431
- }
432
-
433
- return currentPackage;
434
- } catch (error) {
435
- logger.error(`Error getting package by path: ${rootPackage}/${packagePath.join('/')}`, error);
436
- return null;
437
- }
438
- }
439
-
440
- public async listAllPackagesAndEntities(): Promise<Package[]> {
441
- try {
442
- const microservices = await listMicroservices();
443
- const result: Package[] = [];
444
- for (const ms of microservices) {
445
- const pkg = await this.getPackageHierarchy(ms);
446
- if (pkg) {
447
- result.push(pkg);
448
- }
449
- }
450
- return result;
451
- } catch (error) {
452
- logger.error('Error in listAllPackagesAndEntities', error);
453
- return [];
454
- }
455
- }
456
-
457
- public async getFlatEntitiesAndAttributes(filters: any): Promise<any[]> {
458
- try {
459
- const microservices = await listMicroservices();
460
- const result: any[] = [];
461
-
462
- for (const microservice of microservices) {
463
- const entityNames = await listMicroserviceEntities(microservice);
464
-
465
- for (const entityName of entityNames) {
466
- const entity = await readEntityFile(microservice, entityName);
467
-
468
- if (!entity) continue;
469
-
470
- if (filters.name && !entity.name.toLowerCase().includes(filters.name.toLowerCase())) {
471
- continue;
472
- }
473
-
474
- result.push({
475
- type: 'entity',
476
- id: entity.uuid,
477
- name: entity.name,
478
- description: entity.description,
479
- package: microservice
480
- });
481
-
482
- for (const attr of entity.attributes || []) {
483
- if (filters.type && attr.type !== filters.type) {
484
- continue;
485
- }
486
-
487
- result.push({
488
- type: 'attribute',
489
- id: `${entity.uuid}_${attr.name}`,
490
- name: attr.name,
491
- description: attr.description,
492
- dataType: attr.type,
493
- required: attr.required,
494
- primaryKey: attr.primaryKey,
495
- entity: entity.name,
496
- package: microservice
497
- });
498
- }
499
- }
500
- }
501
-
502
- return result;
503
- } catch (error) {
504
- logger.error('Error getting flat entities and attributes', error);
505
- return [];
506
- }
507
- }
508
-
509
- public async getEntityHierarchy(microservice: string, entityName: string): Promise<any> {
510
- try {
511
- const entity = await readEntityFile(microservice, entityName);
512
-
513
- if (!entity) {
514
- return null;
515
- }
516
-
517
- const hierarchy: any = {
518
- id: entity.uuid,
519
- name: entity.name,
520
- description: entity.description,
521
- type: 'entity',
522
- package: microservice,
523
- attributes: entity.attributes || [],
524
- children: [] as any[]
525
- };
526
-
527
- // Read relationships from package-level file
528
- const packagePath = path.join(DATA_DICTIONARIES_DIR, 'microservices', microservice);
529
- const relationships = await readRelationshipsFile(packagePath);
530
-
531
- const children: any[] = [];
532
- for (const rel of relationships) {
533
- let targetUuid: string | null = null;
534
- let relLabel: string = '';
535
-
536
- if (rel.source.entity === entity.uuid) {
537
- targetUuid = rel.target.entity;
538
- relLabel = rel.target.name || '';
539
- } else if (rel.target.entity === entity.uuid) {
540
- targetUuid = rel.source.entity;
541
- relLabel = rel.source.name || '';
542
- }
543
-
544
- if (targetUuid) {
545
- // Find the target entity
546
- const entities = await this.getServiceEntities(microservice);
547
- const relatedEntity = entities.find(e => e.uuid === targetUuid);
548
-
549
- if (relatedEntity) {
550
- children.push({
551
- id: relatedEntity.uuid,
552
- name: relatedEntity.name,
553
- description: relatedEntity.description,
554
- type: 'entity',
555
- package: microservice,
556
- sourceCardinality: rel.source.entity === entity.uuid ? rel.source.cardinality : rel.target.cardinality,
557
- targetCardinality: rel.source.entity === entity.uuid ? rel.target.cardinality : rel.source.cardinality,
558
- relationshipName: relLabel
559
- });
560
- }
561
- }
562
- }
563
- hierarchy.children = children;
564
-
565
- return hierarchy;
566
- } catch (error) {
567
- logger.error(`Error getting entity hierarchy: ${microservice}.${entityName}`, error);
568
- return null;
569
- }
570
- }
571
-
572
- private async getServiceEntities(service: string): Promise<Entity[]> {
573
- const entityNames = await listMicroserviceEntities(service);
574
- const entities: Entity[] = [];
575
- for (const name of entityNames) {
576
- const entity = await readEntityFile(service, name);
577
- if (entity) entities.push(entity);
578
- }
579
- return entities;
580
- }
581
- }
582
- export const dictionaryService = new DictionaryService();
@@ -1,102 +0,0 @@
1
- import { Entity, Relationship, validateEntity, validateRelationship } from '../models/EntitySchema.js';
2
- import { readEntityFile, writeEntityFile, listAllEntities, readRelationshipsFile, getPackagePath } from '../utils/fileOperations.js';
3
- import { logger } from '../utils/logger.js';
4
-
5
- /**
6
- * Service for managing entities and their relationships
7
- */
8
- export class EntityService {
9
- /**
10
- * Validates an entity's structure and data
11
- */
12
- validateEntity(entity: Entity): { valid: boolean; errors: string[] } {
13
- return validateEntity(entity);
14
- }
15
-
16
- /**
17
- * Validates relationships at the package level
18
- */
19
- async validateRelationships(packageName: string, relationships: Relationship[]): Promise<{ valid: boolean; errors: string[] }> {
20
- const errors: string[] = [];
21
-
22
- for (const rel of relationships) {
23
- const relValidation = validateRelationship(rel);
24
- if (!relValidation.valid) {
25
- errors.push(...relValidation.errors);
26
- }
27
- }
28
-
29
- return {
30
- valid: errors.length === 0,
31
- errors
32
- };
33
- }
34
-
35
- /**
36
- * Creates or updates an entity
37
- */
38
- async saveEntity(entity: Entity, packageName: string): Promise<{ success: boolean; errors: string[] }> {
39
- const structureValidation = this.validateEntity(entity);
40
- if (!structureValidation.valid) {
41
- return {
42
- success: false,
43
- errors: structureValidation.errors
44
- };
45
- }
46
-
47
- const saved = await writeEntityFile(entity, packageName);
48
-
49
- return {
50
- success: saved,
51
- errors: saved ? [] : ['Failed to write entity file']
52
- };
53
- }
54
-
55
- /**
56
- * Gets an entity by package and name
57
- */
58
- async getEntity(packageName: string, entityName: string): Promise<Entity | null> {
59
- return await readEntityFile(packageName, entityName);
60
- }
61
-
62
- /**
63
- * Gets all entities that have a relationship with the specified entity
64
- * Uses package-level relationships.yaml files
65
- */
66
- async getRelatedEntities(packageName: string, entityName: string): Promise<Entity[]> {
67
- const entity = await readEntityFile(packageName, entityName);
68
- if (!entity) return [];
69
-
70
- const entityUuid = entity.uuid;
71
- const packagePath = getPackagePath(packageName);
72
- const relationships = await readRelationshipsFile(packagePath);
73
- const relatedEntities: Entity[] = [];
74
-
75
- // Find relationships involving this entity
76
- for (const rel of relationships) {
77
- let targetUuid: string | null = null;
78
-
79
- if (rel.source.entity === entityUuid) {
80
- targetUuid = rel.target.entity;
81
- } else if (rel.target.entity === entityUuid) {
82
- targetUuid = rel.source.entity;
83
- }
84
-
85
- if (targetUuid) {
86
- // Find the entity by UUID across all entities in the package
87
- const allEntities = await listAllEntities();
88
- for (const entityInfo of allEntities) {
89
- const candidate = await readEntityFile(entityInfo.microservice, entityInfo.name);
90
- if (candidate && candidate.uuid === targetUuid && !relatedEntities.some(e => e.uuid === candidate.uuid)) {
91
- relatedEntities.push(candidate);
92
- }
93
- }
94
- }
95
- }
96
-
97
- return relatedEntities;
98
- }
99
- }
100
-
101
- // Export a singleton instance
102
- export const entityService = new EntityService();