@hamak/smart-data-dico 1.0.0

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 (50) hide show
  1. package/README.md +292 -0
  2. package/backend/package.json +51 -0
  3. package/backend/src/__tests__/integration/api.test.ts +149 -0
  4. package/backend/src/__tests__/setup.ts +24 -0
  5. package/backend/src/__tests__/utils/testUtils.ts +76 -0
  6. package/backend/src/adapters/EntityFileAdapter.ts +154 -0
  7. package/backend/src/adapters/YamlFileInfoEnricher.ts +52 -0
  8. package/backend/src/controllers/authController.ts +131 -0
  9. package/backend/src/controllers/diagramController.ts +143 -0
  10. package/backend/src/controllers/dictionaryController.ts +306 -0
  11. package/backend/src/controllers/importExportController.ts +64 -0
  12. package/backend/src/controllers/perspectiveController.ts +90 -0
  13. package/backend/src/controllers/serviceController.ts +418 -0
  14. package/backend/src/controllers/stereotypeController.ts +59 -0
  15. package/backend/src/controllers/versionController.ts +226 -0
  16. package/backend/src/kernel/config.ts +43 -0
  17. package/backend/src/middleware/auth.ts +128 -0
  18. package/backend/src/middleware/jwtAuth.ts +100 -0
  19. package/backend/src/models/Dictionary.ts +38 -0
  20. package/backend/src/models/EntitySchema.ts +393 -0
  21. package/backend/src/models/__tests__/Dictionary.test.ts +92 -0
  22. package/backend/src/models/__tests__/EntitySchema.test.ts +119 -0
  23. package/backend/src/routes/index.ts +120 -0
  24. package/backend/src/scripts/migrate-to-uuid.ts +24 -0
  25. package/backend/src/server.ts +140 -0
  26. package/backend/src/services/__mocks__/entityService.ts +38 -0
  27. package/backend/src/services/__mocks__/serviceService.ts +88 -0
  28. package/backend/src/services/__mocks__/versionService.ts +38 -0
  29. package/backend/src/services/__tests__/dictionaryService.test.ts +74 -0
  30. package/backend/src/services/diagramService.ts +165 -0
  31. package/backend/src/services/dictionaryService.ts +582 -0
  32. package/backend/src/services/entityService.ts +102 -0
  33. package/backend/src/services/exportService.ts +172 -0
  34. package/backend/src/services/importService.ts +208 -0
  35. package/backend/src/services/perspectiveService.ts +276 -0
  36. package/backend/src/services/qualityService.ts +121 -0
  37. package/backend/src/services/serviceService.ts +763 -0
  38. package/backend/src/services/stereotypeService.ts +98 -0
  39. package/backend/src/services/versionService.ts +135 -0
  40. package/backend/src/setupTests.ts +12 -0
  41. package/backend/src/utils/__mocks__/fileOperations.ts +116 -0
  42. package/backend/src/utils/fileOperations.ts +602 -0
  43. package/backend/src/utils/logger.ts +38 -0
  44. package/backend/src/utils/migration.ts +254 -0
  45. package/backend/src/utils/swagger.ts +358 -0
  46. package/backend/src/utils/uuid.ts +41 -0
  47. package/backend/tsconfig.json +20 -0
  48. package/bin/cli.js +129 -0
  49. package/data-dictionaries/stereotypes.yaml +91 -0
  50. package/package.json +42 -0
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Centralized Configuration
3
+ *
4
+ * Single source of truth for backend configuration values.
5
+ * Reads from environment variables with sensible defaults.
6
+ */
7
+
8
+ import path from 'path';
9
+ import dotenv from 'dotenv';
10
+
11
+ dotenv.config();
12
+
13
+ const isProduction = process.env.NODE_ENV === 'production';
14
+
15
+ export const config = {
16
+ /** Server port */
17
+ port: parseInt(process.env.PORT || '3001', 10),
18
+
19
+ /** Base directory for data dictionaries (YAML files) */
20
+ dataDir: process.env.DATA_DIR
21
+ || (isProduction
22
+ ? path.join(process.cwd(), 'data-dictionaries')
23
+ : path.join(process.cwd(), '..', 'data-dictionaries')),
24
+
25
+ /** Deployment profile: local | team | server */
26
+ profile: (process.env.PROFILE || 'local') as 'local' | 'team' | 'server',
27
+
28
+ /** JWT configuration */
29
+ jwt: {
30
+ secret: process.env.JWT_SECRET || 'dev-secret-key',
31
+ expiresIn: process.env.JWT_EXPIRES_IN || '24h',
32
+ },
33
+
34
+ /** Git configuration */
35
+ git: {
36
+ /** Whether to auto-commit on entity changes */
37
+ autoCommit: process.env.GIT_AUTO_COMMIT !== 'false',
38
+ },
39
+
40
+ /** Environment */
41
+ nodeEnv: process.env.NODE_ENV || 'development',
42
+ isProduction,
43
+ };
@@ -0,0 +1,128 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { logger } from '../utils/logger.js';
3
+
4
+ // Interface for user roles
5
+ export enum UserRole {
6
+ ADMIN = 'admin',
7
+ EDITOR = 'editor',
8
+ VIEWER = 'viewer'
9
+ }
10
+
11
+ // Interface for user information
12
+ export interface User {
13
+ id: string;
14
+ username: string;
15
+ role: UserRole;
16
+ }
17
+
18
+ /**
19
+ * Mock user database - in a real application, this would be stored in a database
20
+ * and passwords would be hashed
21
+ */
22
+ const users: Record<string, { password: string; user: User }> = {
23
+ 'admin': {
24
+ password: 'admin123',
25
+ user: {
26
+ id: '1',
27
+ username: 'admin',
28
+ role: UserRole.ADMIN
29
+ }
30
+ },
31
+ 'editor': {
32
+ password: 'editor123',
33
+ user: {
34
+ id: '2',
35
+ username: 'editor',
36
+ role: UserRole.EDITOR
37
+ }
38
+ },
39
+ 'viewer': {
40
+ password: 'viewer123',
41
+ user: {
42
+ id: '3',
43
+ username: 'viewer',
44
+ role: UserRole.VIEWER
45
+ }
46
+ }
47
+ };
48
+
49
+ /**
50
+ * Basic authentication middleware
51
+ * Extracts credentials from Authorization header and validates them
52
+ * @param req Express request
53
+ * @param res Express response
54
+ * @param next Next function
55
+ */
56
+ export const basicAuth = (req: Request, res: Response, next: NextFunction) => {
57
+ // Get authorization header
58
+ const authHeader = req.headers.authorization;
59
+
60
+ if (!authHeader || !authHeader.startsWith('Basic ')) {
61
+ return res.status(401).json({
62
+ message: 'Authentication required',
63
+ error: 'Missing or invalid Authorization header'
64
+ });
65
+ }
66
+
67
+ // Extract and decode credentials
68
+ try {
69
+ const base64Credentials = authHeader.split(' ')[1];
70
+ const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8');
71
+ const [username, password] = credentials.split(':');
72
+
73
+ // Validate credentials
74
+ const userRecord = users[username];
75
+
76
+ if (!userRecord || userRecord.password !== password) {
77
+ return res.status(401).json({
78
+ message: 'Authentication failed',
79
+ error: 'Invalid username or password'
80
+ });
81
+ }
82
+
83
+ // Add user to request object
84
+ (req as any).user = userRecord.user;
85
+
86
+ next();
87
+ } catch (error) {
88
+ logger.error(`Authentication error: ${error}`);
89
+ return res.status(401).json({
90
+ message: 'Authentication failed',
91
+ error: 'Invalid credentials format'
92
+ });
93
+ }
94
+ };
95
+
96
+ /**
97
+ * Role-based authorization middleware
98
+ * Checks if the authenticated user has one of the required roles
99
+ * @param allowedRoles Array of allowed roles
100
+ * @returns Middleware function
101
+ */
102
+ export const authenticate = (allowedRoles: UserRole[]) => {
103
+ return (req: Request, res: Response, next: NextFunction) => {
104
+ // First apply basic authentication
105
+ basicAuth(req, res, (err) => {
106
+ if (err) return next(err);
107
+
108
+ // Check if user has required role
109
+ const user = (req as any).user as User;
110
+
111
+ if (!user) {
112
+ return res.status(401).json({
113
+ message: 'Authentication required',
114
+ error: 'User not authenticated'
115
+ });
116
+ }
117
+
118
+ if (!allowedRoles.includes(user.role)) {
119
+ return res.status(403).json({
120
+ message: 'Access denied',
121
+ error: 'Insufficient permissions'
122
+ });
123
+ }
124
+
125
+ next();
126
+ });
127
+ };
128
+ };
@@ -0,0 +1,100 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import jwt from 'jsonwebtoken';
3
+ import { logger } from '../utils/logger.js';
4
+ import { User } from './auth.js';
5
+
6
+ // JWT secret key - should be in environment variables in production
7
+ const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
8
+
9
+ /**
10
+ * JWT authentication middleware
11
+ * Verifies JWT token from Authorization header
12
+ * @param req Express request
13
+ * @param res Express response
14
+ * @param next Next function
15
+ */
16
+ export const verifyToken = (req: Request, res: Response, next: NextFunction) => {
17
+ // Development mode bypass for testing
18
+ if (process.env.NODE_ENV === 'development' && req.headers.authorization === 'Bearer mock-token-for-testing') {
19
+ logger.info('Using mock authentication token for development');
20
+ // Set a mock user for development
21
+ (req as any).user = {
22
+ id: 'dev-user',
23
+ username: 'developer',
24
+ email: 'dev@example.com',
25
+ role: 'admin'
26
+ };
27
+ return next();
28
+ }
29
+
30
+ // Get authorization header
31
+ const authHeader = req.headers.authorization;
32
+
33
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
34
+ return res.status(401).json({
35
+ message: 'Authentication required',
36
+ error: 'Missing or invalid Authorization header'
37
+ });
38
+ }
39
+
40
+ // Extract token
41
+ const token = authHeader.split(' ')[1];
42
+
43
+ try {
44
+ // Verify token
45
+ const decoded = jwt.verify(token, JWT_SECRET as jwt.Secret) as User;
46
+
47
+ // Add user to request object
48
+ (req as any).user = decoded;
49
+
50
+ next();
51
+ } catch (error: any) {
52
+ logger.error(`JWT verification error: ${error.message}`);
53
+
54
+ if (error.name === 'TokenExpiredError') {
55
+ return res.status(401).json({
56
+ message: 'Authentication failed',
57
+ error: 'Token expired'
58
+ });
59
+ }
60
+
61
+ return res.status(401).json({
62
+ message: 'Authentication failed',
63
+ error: 'Invalid token'
64
+ });
65
+ }
66
+ };
67
+
68
+ /**
69
+ * Role-based authorization middleware using JWT
70
+ * Checks if the authenticated user has one of the required roles
71
+ * @param allowedRoles Array of allowed roles
72
+ * @returns Middleware function
73
+ */
74
+ export const authorizeJwt = (allowedRoles: string[]) => {
75
+ return (req: Request, res: Response, next: NextFunction) => {
76
+ // First verify token
77
+ verifyToken(req, res, (err) => {
78
+ if (err) return next(err);
79
+
80
+ // Check if user has required role
81
+ const user = (req as any).user as User;
82
+
83
+ if (!user) {
84
+ return res.status(401).json({
85
+ message: 'Authentication required',
86
+ error: 'User not authenticated'
87
+ });
88
+ }
89
+
90
+ if (!allowedRoles.includes(user.role)) {
91
+ return res.status(403).json({
92
+ message: 'Access denied',
93
+ error: 'Insufficient permissions'
94
+ });
95
+ }
96
+
97
+ next();
98
+ });
99
+ };
100
+ };
@@ -0,0 +1,38 @@
1
+ import { Entity, MetadataDefinition, MetadataEntry, Relationship } from './EntitySchema.js';
2
+
3
+ /**
4
+ * Package type annotation
5
+ */
6
+ export enum PackageType {
7
+ PROJECT = 'project',
8
+ MICROSERVICE = 'microservice',
9
+ MODULE = 'module'
10
+ }
11
+
12
+ /**
13
+ * Represents a hierarchical package, which can contain subpackages and/or entities.
14
+ */
15
+ export interface Package {
16
+ id: string;
17
+ name: string;
18
+ description?: string;
19
+ type?: PackageType | string;
20
+ entities: Entity[];
21
+ subPackages: Package[];
22
+ relationships: Relationship[];
23
+ metadata?: MetadataEntry[];
24
+ }
25
+
26
+ /**
27
+ * Dictionary model interface.
28
+ * Supports a hierarchy of packages via the rootPackage property.
29
+ */
30
+ export interface Dictionary {
31
+ id: string;
32
+ name: string;
33
+ description?: string;
34
+ metadataDefinitions?: MetadataDefinition[];
35
+ createdAt?: Date;
36
+ updatedAt?: Date;
37
+ rootPackage: Package;
38
+ }
@@ -0,0 +1,393 @@
1
+ import { Validator } from 'jsonschema';
2
+ import { generateUUID, isValidUUID } from '../utils/uuid.js';
3
+ type Schema = any; // Using any as a workaround for missing type definitions
4
+
5
+ /**
6
+ * Supported attribute types in the data dictionary
7
+ */
8
+ export enum AttributeType {
9
+ STRING = 'string',
10
+ NUMBER = 'number',
11
+ INTEGER = 'integer',
12
+ BOOLEAN = 'boolean',
13
+ DATETIME = 'datetime',
14
+ DATE = 'date',
15
+ TIME = 'time',
16
+ DATE_TIME = 'date-time',
17
+ TIMESTAMP = 'timestamp',
18
+ DURATION = 'duration',
19
+ ENUM = 'enum',
20
+ OBJECT = 'object',
21
+ ARRAY = 'array'
22
+ }
23
+
24
+ /**
25
+ * Cardinality for relationship ends
26
+ */
27
+ export enum Cardinality {
28
+ ONE = 'one',
29
+ MANY = 'many'
30
+ }
31
+
32
+ /**
33
+ * Metadata value types for typed metadata definitions
34
+ */
35
+ export enum MetadataValueType {
36
+ STRING = 'string',
37
+ NUMBER = 'number',
38
+ BOOLEAN = 'boolean',
39
+ DATE = 'date',
40
+ FLAG = 'flag',
41
+ RULE = 'rule',
42
+ }
43
+
44
+ export enum RuleSeverity {
45
+ INFO = 'info',
46
+ WARNING = 'warning',
47
+ ERROR = 'error',
48
+ }
49
+
50
+ /**
51
+ * A metadata definition (schema for metadata entries)
52
+ */
53
+ export interface MetadataDefinition {
54
+ name: string;
55
+ type: MetadataValueType;
56
+ description?: string;
57
+ required?: boolean;
58
+ }
59
+
60
+ /**
61
+ * A typed metadata entry
62
+ */
63
+ export interface MetadataEntry {
64
+ name: string;
65
+ value: string | number | boolean;
66
+ severity?: RuleSeverity;
67
+ }
68
+
69
+ export enum EntityStatus {
70
+ DRAFT = 'draft',
71
+ SUBMITTED = 'submitted',
72
+ APPROVED = 'approved',
73
+ RETURNED = 'returned',
74
+ }
75
+
76
+ export interface ReviewComment {
77
+ id: string;
78
+ author: string;
79
+ timestamp: string;
80
+ message: string;
81
+ targetField?: string;
82
+ resolved?: boolean;
83
+ }
84
+
85
+ export type StereotypeTarget = 'package' | 'entity' | 'attribute';
86
+
87
+ export interface Stereotype {
88
+ id: string;
89
+ name: string;
90
+ description?: string;
91
+ appliesTo: StereotypeTarget;
92
+ metadataDefinitions: MetadataDefinition[];
93
+ }
94
+
95
+ /**
96
+ * A node in a perspective — annotates, sets frontier, or excludes a path
97
+ */
98
+ export interface PerspectiveNode {
99
+ path: string;
100
+ traverse?: boolean;
101
+ exclude?: boolean;
102
+ metadata?: MetadataEntry[];
103
+ }
104
+
105
+ /**
106
+ * A perspective is a business view over a subset of the data model
107
+ */
108
+ export interface Perspective {
109
+ uuid: string;
110
+ name: string;
111
+ description?: string;
112
+ rootEntities: string[];
113
+ nodes?: PerspectiveNode[];
114
+ maxDepth?: number;
115
+ metadata?: MetadataEntry[];
116
+ createdAt?: string;
117
+ updatedAt?: string;
118
+ }
119
+
120
+ /**
121
+ * A resolved node from BFS traversal of a perspective
122
+ */
123
+ export interface ResolvedNode {
124
+ entityUuid: string;
125
+ entityName: string;
126
+ service: string;
127
+ path: string;
128
+ hopDistance: number;
129
+ isRoot: boolean;
130
+ isFrontier: boolean;
131
+ isManualInclusion: boolean;
132
+ }
133
+
134
+ /**
135
+ * A perspective with its resolved entity graph
136
+ */
137
+ export interface ResolvedPerspective extends Perspective {
138
+ resolvedNodes: ResolvedNode[];
139
+ }
140
+
141
+ /**
142
+ * Grouped constraint fields for attributes
143
+ */
144
+ export interface AttributeConstraints {
145
+ minLength?: number;
146
+ maxLength?: number;
147
+ pattern?: string;
148
+ format?: string;
149
+ minimum?: number;
150
+ maximum?: number;
151
+ precision?: number;
152
+ scale?: number;
153
+ enumValues?: string[];
154
+ }
155
+
156
+ /**
157
+ * Interface for entity attribute definition
158
+ */
159
+ export interface Attribute {
160
+ uuid: string;
161
+ name: string;
162
+ description: string;
163
+ type: AttributeType;
164
+ required: boolean;
165
+ unique?: boolean;
166
+ primaryKey?: boolean;
167
+ defaultValue?: any;
168
+ examples?: any[];
169
+ constraints?: AttributeConstraints;
170
+
171
+ // For object and array types
172
+ items?: Attribute;
173
+ properties?: Attribute[];
174
+
175
+ // Typed metadata
176
+ metadata?: MetadataEntry[];
177
+ }
178
+
179
+ /**
180
+ * One end of a relationship
181
+ */
182
+ export interface RelationshipEnd {
183
+ entity: string; // UUID of the entity
184
+ cardinality: Cardinality;
185
+ name?: string; // Navigation property name
186
+ referenceAttributes?: string[];
187
+ }
188
+
189
+ /**
190
+ * A relationship between two entities, stored at package level
191
+ */
192
+ export type RelationshipType = 'structural' | 'lineage';
193
+
194
+ export interface Relationship {
195
+ uuid: string;
196
+ description?: string;
197
+ type?: RelationshipType;
198
+ source: RelationshipEnd;
199
+ target: RelationshipEnd;
200
+ metadata?: MetadataEntry[];
201
+ }
202
+
203
+ export interface LineageNode {
204
+ entityUuid: string;
205
+ entityName: string;
206
+ service: string;
207
+ direction: 'upstream' | 'downstream';
208
+ depth: number;
209
+ relationship: { uuid: string; description: string };
210
+ }
211
+
212
+ export interface LineageResult {
213
+ entity: { uuid: string; name: string; service: string };
214
+ upstream: LineageNode[];
215
+ downstream: LineageNode[];
216
+ }
217
+
218
+ /**
219
+ * Interface for entity definition
220
+ */
221
+ export interface Entity {
222
+ uuid: string;
223
+ name: string;
224
+ description?: string;
225
+ stereotype?: string;
226
+ status?: EntityStatus;
227
+ attributes: Attribute[];
228
+ metadata?: MetadataEntry[];
229
+ createdAt?: string;
230
+ updatedAt?: string;
231
+ }
232
+
233
+ // Backward-compatible alias
234
+ export type EntityAttribute = Attribute;
235
+
236
+ /**
237
+ * JSON Schema for validating entity definitions
238
+ */
239
+ export const entitySchema: Schema = {
240
+ type: 'object',
241
+ required: ['uuid', 'name', 'attributes'],
242
+ properties: {
243
+ uuid: { type: 'string', pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' },
244
+ name: { type: 'string' },
245
+ description: { type: 'string' },
246
+ attributes: {
247
+ type: 'array',
248
+ items: {
249
+ type: 'object',
250
+ required: ['uuid', 'name', 'description', 'type', 'required'],
251
+ properties: {
252
+ uuid: { type: 'string', pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' },
253
+ name: { type: 'string' },
254
+ description: { type: 'string' },
255
+ type: {
256
+ type: 'string',
257
+ enum: Object.values(AttributeType)
258
+ },
259
+ required: { type: 'boolean' },
260
+ unique: { type: 'boolean' },
261
+ primaryKey: { type: 'boolean' },
262
+ defaultValue: { },
263
+ examples: { type: 'array' },
264
+ constraints: {
265
+ type: 'object',
266
+ properties: {
267
+ minLength: { type: 'integer', minimum: 0 },
268
+ maxLength: { type: 'integer', minimum: 0 },
269
+ pattern: { type: 'string' },
270
+ format: { type: 'string' },
271
+ minimum: { type: 'number' },
272
+ maximum: { type: 'number' },
273
+ precision: { type: 'integer', minimum: 0 },
274
+ scale: { type: 'integer', minimum: 0 },
275
+ enumValues: {
276
+ type: 'array',
277
+ items: { type: 'string' }
278
+ }
279
+ }
280
+ },
281
+ items: { type: 'object' },
282
+ properties: { type: 'array' },
283
+ metadata: { type: 'array' }
284
+ }
285
+ }
286
+ },
287
+ metadata: { type: 'array' },
288
+ createdAt: { type: 'string', format: 'date-time' },
289
+ updatedAt: { type: 'string', format: 'date-time' }
290
+ }
291
+ };
292
+
293
+ /**
294
+ * JSON Schema for validating relationship definitions
295
+ */
296
+ export const relationshipSchema: Schema = {
297
+ type: 'object',
298
+ required: ['uuid', 'source', 'target'],
299
+ properties: {
300
+ uuid: { type: 'string', pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' },
301
+ description: { type: 'string' },
302
+ source: {
303
+ type: 'object',
304
+ required: ['entity', 'cardinality'],
305
+ properties: {
306
+ entity: { type: 'string' },
307
+ cardinality: { type: 'string', enum: Object.values(Cardinality) },
308
+ name: { type: 'string' },
309
+ referenceAttributes: { type: 'array', items: { type: 'string' } }
310
+ }
311
+ },
312
+ target: {
313
+ type: 'object',
314
+ required: ['entity', 'cardinality'],
315
+ properties: {
316
+ entity: { type: 'string' },
317
+ cardinality: { type: 'string', enum: Object.values(Cardinality) },
318
+ name: { type: 'string' },
319
+ referenceAttributes: { type: 'array', items: { type: 'string' } }
320
+ }
321
+ },
322
+ metadata: { type: 'array' }
323
+ }
324
+ };
325
+
326
+ /**
327
+ * Validates an entity definition against the schema
328
+ */
329
+ export function validateEntity(entity: Entity): { valid: boolean; errors: string[] } {
330
+ const validator = new Validator();
331
+ const result = validator.validate(entity, entitySchema);
332
+
333
+ const errors: string[] = result.errors.map((error: any) => error.stack);
334
+
335
+ // Additional UUID validation
336
+ if (!isValidUUID(entity.uuid)) {
337
+ errors.push('Entity UUID is invalid');
338
+ }
339
+
340
+ // Validate attribute UUIDs
341
+ if (entity.attributes) {
342
+ entity.attributes.forEach((attr, index) => {
343
+ if (!isValidUUID(attr.uuid)) {
344
+ errors.push(`Attribute ${index} UUID is invalid`);
345
+ }
346
+ });
347
+ }
348
+
349
+ return {
350
+ valid: result.valid && errors.length === 0,
351
+ errors
352
+ };
353
+ }
354
+
355
+ /**
356
+ * Validates a relationship definition against the schema
357
+ */
358
+ export function validateRelationship(relationship: Relationship): { valid: boolean; errors: string[] } {
359
+ const validator = new Validator();
360
+ const result = validator.validate(relationship, relationshipSchema);
361
+
362
+ const errors: string[] = result.errors.map((error: any) => error.stack);
363
+
364
+ if (!isValidUUID(relationship.uuid)) {
365
+ errors.push('Relationship UUID is invalid');
366
+ }
367
+
368
+ return {
369
+ valid: result.valid && errors.length === 0,
370
+ errors
371
+ };
372
+ }
373
+
374
+ /**
375
+ * Creates a new entity with UUIDs for all components
376
+ */
377
+ export function createEntityWithUUIDs(entityData: Omit<Entity, 'uuid' | 'attributes'> & {
378
+ attributes: Omit<Attribute, 'uuid'>[];
379
+ }): Entity {
380
+ return {
381
+ ...entityData,
382
+ uuid: generateUUID(),
383
+ attributes: entityData.attributes.map(attr => ({
384
+ ...attr,
385
+ uuid: generateUUID(),
386
+ properties: attr.properties ? attr.properties.map(prop => ({
387
+ ...prop,
388
+ uuid: prop.uuid || generateUUID()
389
+ })) : attr.properties,
390
+ items: attr.items ? { ...attr.items, uuid: attr.items.uuid || generateUUID() } : attr.items
391
+ }))
392
+ };
393
+ }