Skip to main content
Glama
system-runtime.ts10.8 kB
/** * System Runtime Transformation Module * Transforms MSON models to System Runtime bundle format */ import type { MsonAttribute, MsonEntity, MsonModel, MsonRelationship, SchemaKind, SystemRuntimeBundle, SystemRuntimeMethodSignature, SystemRuntimeModel, SystemRuntimeSchema, } from '../types.js'; /** * Generate a unique ID for System Runtime objects * @param prefix - Optional prefix for the ID * @returns A unique identifier string */ export function generateId(prefix: string = ''): string { const timestamp = Date.now().toString(36); const random = Math.random().toString(36).substring(2, 15); return `${prefix}${timestamp}${random}`; } /** * Determine if an attribute should be a property, link, or collection * @param attribute - The MSON attribute to analyze * @param relationships - All relationships in the model * @param entityId - The ID of the entity containing this attribute * @returns The schema kind (property/link/collection) */ export function determineAttributeKind( attribute: MsonAttribute, relationships: MsonRelationship[], entityId: string ): SchemaKind { // Check if this attribute is involved in a relationship const relatedRelationship = relationships.find( (rel) => (rel.from === entityId || rel.to === entityId) && (rel.type === 'association' || rel.type === 'aggregation' || rel.type === 'composition') ); if (!relatedRelationship) { // No relationship, check if type looks like an entity reference const primitiveTypes = ['string', 'number', 'boolean', 'date', 'any', 'object', 'array']; const isPrimitiveType = primitiveTypes.includes(attribute.type.toLowerCase()); return isPrimitiveType ? 'property' : 'link'; } // Has relationship, check multiplicity const multiplicity = relatedRelationship.multiplicity?.to || '1'; const isCollection = multiplicity.includes('*') || multiplicity.includes('..'); return isCollection ? 'collection' : 'link'; } /** * Find an entity by ID in the model * @param entities - Array of entities to search * @param entityId - The ID to find * @returns The entity or undefined */ function findEntityById(entities: MsonEntity[], entityId: string): MsonEntity | undefined { return entities.find((e) => e.id === entityId); } /** * Convert MSON entity to System Runtime schema * Enhanced to handle bidirectional relationships and complex scenarios * @param entity - The MSON entity to convert * @param relationships - All relationships in the model * @param allEntities - All entities in the model (for name lookup) * @returns System Runtime schema definition */ export function entityToSchema( entity: MsonEntity, relationships: MsonRelationship[], allEntities: MsonEntity[] ): SystemRuntimeSchema { const schema: SystemRuntimeSchema = { _id: generateId('s'), _name: entity.name, _inherit: ['_Component'], }; // Add inheritance relationships (multiple inheritance support) const inheritanceRels = relationships.filter( (rel) => rel.from === entity.id && (rel.type === 'inheritance' || rel.type === 'implementation') ); if (inheritanceRels.length > 0) { const inheritedNames = inheritanceRels .map((rel) => { const targetEntity = findEntityById(allEntities, rel.to); return targetEntity?.name || rel.to; }) .filter((name) => name !== '_Component'); // Remove duplicates and ensure _Component is first const uniqueInherited = Array.from(new Set(inheritedNames)); schema._inherit = ['_Component', ...uniqueInherited]; } // Add attributes for (const attr of entity.attributes) { const kind = determineAttributeKind(attr, relationships, entity.id); schema[attr.name] = kind; } // Add methods for (const method of entity.methods) { schema[method.name] = 'method'; } // Add relationships as links/collections (handles bidirectional) const processedRelationships = new Set<string>(); for (const rel of relationships) { // Skip if already processed (avoid duplicates in bidirectional relationships) if (processedRelationships.has(rel.id)) { continue; } // Process relationships where this entity is the source if ( rel.from === entity.id && (rel.type === 'association' || rel.type === 'aggregation' || rel.type === 'composition') ) { const multiplicity = rel.multiplicity?.to || '1'; const isCollection = multiplicity.includes('*') || multiplicity.includes('..'); const targetEntity = findEntityById(allEntities, rel.to); // Use relationship name if provided, otherwise generate from target entity const relationshipName = rel.name || (isCollection ? `${targetEntity?.name.toLowerCase() || rel.to}s` : targetEntity?.name.toLowerCase() || rel.to); schema[relationshipName] = isCollection ? 'collection' : 'link'; processedRelationships.add(rel.id); } // Process relationships where this entity is the target (bidirectional) if ( rel.to === entity.id && (rel.type === 'association' || rel.type === 'aggregation' || rel.type === 'composition') ) { const multiplicity = rel.multiplicity?.from || '1'; const isCollection = multiplicity.includes('*') || multiplicity.includes('..'); const sourceEntity = findEntityById(allEntities, rel.from); // For reverse relationships, use a different naming convention const relationshipName = isCollection ? `${sourceEntity?.name.toLowerCase() || rel.from}s` : sourceEntity?.name.toLowerCase() || rel.from; // Only add if not already defined (avoid conflicts with explicit attributes) if (!(relationshipName in schema)) { schema[relationshipName] = isCollection ? 'collection' : 'link'; } processedRelationships.add(rel.id); } } return schema; } /** * Convert MSON entity to System Runtime model override * Enhanced to handle bidirectional relationships and complex scenarios * @param entity - The MSON entity to convert * @param relationships - All relationships in the model * @param allEntities - All entities in the model (for name lookup) * @returns System Runtime model definition */ export function entityToModel( entity: MsonEntity, relationships: MsonRelationship[], allEntities: MsonEntity[] ): SystemRuntimeModel { const model: SystemRuntimeModel = { _id: generateId('m'), _name: entity.name, }; // Add attribute types for (const attr of entity.attributes) { const kind = determineAttributeKind(attr, relationships, entity.id); if (kind === 'property') { model[attr.name] = attr.type; } else if (kind === 'link') { model[attr.name] = attr.type; // Entity name } else if (kind === 'collection') { model[attr.name] = [attr.type]; // Array of entity name } } // Add method signatures for (const method of entity.methods) { const signature: SystemRuntimeMethodSignature = { '=>': method.returnType, }; for (const param of method.parameters) { signature[param.name] = param.type; } model[method.name] = signature; } // Add relationship types (handles bidirectional) const processedRelationships = new Set<string>(); for (const rel of relationships) { // Skip if already processed (avoid duplicates in bidirectional relationships) if (processedRelationships.has(rel.id)) { continue; } // Process relationships where this entity is the source if ( rel.from === entity.id && (rel.type === 'association' || rel.type === 'aggregation' || rel.type === 'composition') ) { const multiplicity = rel.multiplicity?.to || '1'; const isCollection = multiplicity.includes('*') || multiplicity.includes('..'); const targetEntity = findEntityById(allEntities, rel.to); const targetName = targetEntity?.name || rel.to; // Use relationship name if provided, otherwise generate from target entity const relationshipName = rel.name || (isCollection ? `${targetName.toLowerCase()}s` : targetName.toLowerCase()); model[relationshipName] = isCollection ? [targetName] : targetName; processedRelationships.add(rel.id); } // Process relationships where this entity is the target (bidirectional) if ( rel.to === entity.id && (rel.type === 'association' || rel.type === 'aggregation' || rel.type === 'composition') ) { const multiplicity = rel.multiplicity?.from || '1'; const isCollection = multiplicity.includes('*') || multiplicity.includes('..'); const sourceEntity = findEntityById(allEntities, rel.from); const sourceName = sourceEntity?.name || rel.from; // For reverse relationships, use a different naming convention const relationshipName = isCollection ? `${sourceName.toLowerCase()}s` : sourceName.toLowerCase(); // Only add if not already defined (avoid conflicts with explicit attributes) if (!(relationshipName in model)) { model[relationshipName] = isCollection ? [sourceName] : sourceName; } processedRelationships.add(rel.id); } } return model; } /** * Generate bundle metadata from MSON model * @param model - The MSON model * @param version - Optional version string (defaults to 0.0.1) * @returns Bundle metadata object */ export function generateBundleMetadata( model: MsonModel, version: string = '0.0.1' ): Pick<SystemRuntimeBundle, '_id' | 'name' | 'description' | 'version' | 'master'> { return { _id: generateId('sys'), name: model.name, description: model.description || `System Runtime bundle for ${model.name}`, version, master: true, }; } /** * Main transformation function: Convert MSON model to System Runtime bundle * @param model - The MSON model to convert * @param version - Optional version string (defaults to 0.0.1) * @returns Complete System Runtime bundle */ export function msonToSystemRuntimeBundle(model: MsonModel, version?: string): SystemRuntimeBundle { const metadata = generateBundleMetadata(model, version); const bundle: SystemRuntimeBundle = { ...metadata, schemas: {}, models: {}, types: {}, behaviors: {}, components: {}, }; // Transform each entity for (const entity of model.entities) { const schema = entityToSchema(entity, model.relationships, model.entities); const modelDef = entityToModel(entity, model.relationships, model.entities); bundle.schemas[schema._id] = schema; bundle.models[modelDef._id] = modelDef; // Initialize empty components section for this entity bundle.components[entity.name] = {}; } return bundle; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chevyfsa/system-designer-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server