Skip to main content
Glama
fairsquare-framework-schema.ts33.6 kB
/** * FairSquare Custom Framework Schema * * Detects custom patterns: * - @Injectable([deps]) with dependency array * - Controller with HTTP method conventions (get, post, put, delete) * - Repository pattern (extends Repository) * - Custom permission managers */ import { Node } from 'ts-morph'; import { FrameworkSchema, CoreNodeType, ParsedNode, ParsingContext } from './schema.js'; // ============================================================================ // FAIRSQUARE SEMANTIC TYPES // ============================================================================ export enum FairSquareSemanticNodeType { // Core FairSquare Types FS_CONTROLLER = 'Controller', FS_SERVICE = 'Service', FS_REPOSITORY = 'Repository', FS_DAL = 'DAL', // Data Access Layer FS_PERMISSION_MANAGER = 'PermissionManager', FS_VENDOR_CLIENT = 'VendorClient', // External service clients // HTTP & Routing FS_ROUTE_DEFINITION = 'RouteDefinition', } export enum FairSquareSemanticEdgeType { // Dependency Injection FS_INJECTS = 'INJECTS', // Repository Pattern FS_REPOSITORY_USES_DAL = 'USES_DAL', // HTTP Routing FS_ROUTES_TO = 'ROUTES_TO', // Route definition → Controller FS_ROUTES_TO_HANDLER = 'ROUTES_TO_HANDLER', // Route definition → Handler method // Permissions FS_PROTECTED_BY = 'PROTECTED_BY', FS_INTERNAL_API_CALL = 'INTERNAL_API_CALL', } // Common labels used across FairSquare schema export enum FairSquareLabel { FAIRSQUARE = 'FairSquare', BUSINESS_LOGIC = 'BusinessLogic', DATA_ACCESS = 'DataAccess', DATABASE = 'Database', SECURITY = 'Security', EXTERNAL_INTEGRATION = 'ExternalIntegration', HTTP_ENDPOINT = 'HttpEndpoint', } // ============================================================================ // CONTEXT EXTRACTORS // ============================================================================ /** * Extract Injectable decorator dependencies * @Injectable([Dep1, Dep2]) → ['Dep1', 'Dep2'] */ const extractInjectableDependencies = ( parsedNode: ParsedNode, _allNodes?: Map<string, ParsedNode>, _sharedContext?: ParsingContext, ): Record<string, any> => { const node = parsedNode.sourceNode; if (!node || !Node.isClassDeclaration(node)) return {}; const decorators = node.getDecorators(); for (const decorator of decorators) { if (decorator.getName() === 'Injectable') { const args = decorator.getArguments(); if (args.length > 0 && Node.isArrayLiteralExpression(args[0])) { const elements = args[0].getElements(); const dependencies = elements.map((el) => el.getText()); return { dependencies, dependencyCount: dependencies.length, hasInjection: dependencies.length > 0, }; } } } return {}; }; /** * Extract repository DAL dependencies * Uses the dependencies array from @Injectable([...]) decorator */ const extractRepositoryDals = ( parsedNode: ParsedNode, _allNodes?: Map<string, ParsedNode>, _sharedContext?: ParsingContext, ): Record<string, any> => { const dependencies = parsedNode.properties.context?.dependencies ?? []; // Filter for DAL-related dependencies const dalDeps = dependencies.filter( (dep: string) => dep.toLowerCase().includes('dal') || dep.toLowerCase().endsWith('dal'), ); return { dals: dalDeps, dalCount: dalDeps.length, usesDALPattern: dalDeps.length > 0, }; }; /** * Extract permission manager usage * Uses the dependencies array from @Injectable([...]) decorator */ const extractPermissionManager = ( parsedNode: ParsedNode, _allNodes?: Map<string, ParsedNode>, _sharedContext?: ParsingContext, ): Record<string, any> => { const dependencies = parsedNode.properties.context?.dependencies ?? []; // Find permission manager dependency const permissionDep = dependencies.find((dep: string) => dep.toLowerCase().includes('permission')); if (permissionDep) { return { permissionManager: permissionDep, hasPermissionManager: true, }; } return {}; }; /** * Extract monorepo project name from file path * Supports patterns like: packages/project-name/..., apps/project-name/... */ const extractMonorepoProject = ( parsedNode: ParsedNode, _allNodes?: Map<string, ParsedNode>, _sharedContext?: ParsingContext, ): Record<string, any> => { const node = parsedNode.sourceNode; if (!node || !Node.isClassDeclaration(node)) return {}; const filePath = node.getSourceFile().getFilePath(); // Match common monorepo patterns const monorepoPatterns = [ /\/packages\/([^/]+)\//, /\/apps\/([^/]+)\//, /\/libs\/([^/]+)\//, /\/components\/([^/]+)\//, ]; for (const pattern of monorepoPatterns) { const match = filePath.match(pattern); if (match?.[1]) { return { monorepoProject: match[1], isMonorepoPackage: true, }; } } return {}; }; /** * Extract route definitions from .routes.ts files * Parses ModuleRoute[] arrays to get explicit route mappings */ const extractRouteDefinitions = ( parsedNode: ParsedNode, _allNodes?: Map<string, ParsedNode>, _sharedContext?: ParsingContext, ): Record<string, any> => { const node = parsedNode.sourceNode; if (!node || !Node.isVariableDeclaration(node)) return {}; // Get the initializer (the array) const initializer = node.getInitializer(); if (!initializer || !Node.isArrayLiteralExpression(initializer)) { return {}; } const routes: any[] = []; // Loop through each object in the array for (const element of initializer.getElements()) { if (!Node.isObjectLiteralExpression(element)) continue; const routeData: any = {}; // Extract each property from the route object for (const prop of element.getProperties()) { if (!Node.isPropertyAssignment(prop)) continue; const propName = prop.getName(); const propValue = prop.getInitializer(); if (!propValue) continue; // Extract based on property type switch (propName) { case 'method': case 'path': case 'handler': // String values routeData[propName] = propValue.getText().replace(/['"]/g, ''); break; case 'authenticated': // Boolean value routeData[propName] = propValue.getText() === 'true'; break; case 'controller': // Identifier (class reference) routeData.controllerName = propValue.getText(); break; } } // Only add if we got meaningful data if (routeData.method && routeData.path) { routes.push(routeData); } } return { routes, routeCount: routes.length, isRouteFile: true, fileName: node.getSourceFile().getBaseName(), }; }; // ============================================================================ // FRAMEWORK ENHANCEMENTS // ============================================================================ export const FAIRSQUARE_FRAMEWORK_SCHEMA: FrameworkSchema = { name: 'FairSquare Custom Framework', version: '1.0.0', description: 'Custom FairSquare dependency injection and repository patterns', enhances: [CoreNodeType.CLASS_DECLARATION, CoreNodeType.METHOD_DECLARATION], metadata: { targetLanguages: ['typescript'], dependencies: ['@fairsquare/core', '@fairsquare/server'], parseVariablesFrom: ['**/*.routes.ts', '**/*.route.ts'], }, // ============================================================================ // GLOBAL CONTEXT EXTRACTORS (run on all nodes) // ============================================================================ contextExtractors: [ { nodeType: CoreNodeType.CLASS_DECLARATION, extractor: extractInjectableDependencies, priority: 10, }, ], // ============================================================================ // NODE ENHANCEMENTS // ============================================================================ enhancements: { // FairSquare Controller fairsquareController: { name: 'FairSquare Controller', targetCoreType: CoreNodeType.CLASS_DECLARATION, semanticType: FairSquareSemanticNodeType.FS_CONTROLLER as any, priority: 100, detectionPatterns: [ { type: 'classname', pattern: /Controller$/, confidence: 0.7, priority: 5, }, { type: 'function', pattern: (parsedNode: ParsedNode) => { const node = parsedNode.sourceNode; if (!node || !Node.isClassDeclaration(node)) return false; const baseClass = node.getExtends(); const result = baseClass?.getText() === 'Controller'; return result; }, confidence: 1.0, priority: 10, }, ], contextExtractors: [ { nodeType: CoreNodeType.CLASS_DECLARATION, extractor: extractInjectableDependencies, priority: 10, }, { nodeType: CoreNodeType.CLASS_DECLARATION, extractor: extractPermissionManager, priority: 8, }, { nodeType: CoreNodeType.CLASS_DECLARATION, extractor: (parsedNode: ParsedNode, allNodes, sharedContext) => { // Store vendor controllers in shared context for later lookup const controllerName = parsedNode.properties.name; // Check if this is a vendor controller if (controllerName.includes('Vendor') || parsedNode.properties.filePath.includes('modules/vendor')) { // Extract vendor name: ExperianVendorController → experian let vendorName = ''; if (controllerName.endsWith('VendorController')) { vendorName = controllerName.replace('VendorController', '').toLowerCase(); } else if (controllerName.endsWith('Controller')) { vendorName = controllerName.replace('Controller', '').toLowerCase(); } if (vendorName) { // Initialize map if not exists if (!sharedContext?.has('vendorControllers')) { sharedContext?.set('vendorControllers', new Map<string, ParsedNode>()); } const vendorControllerMap = sharedContext?.get('vendorControllers') as Map<string, ParsedNode>; vendorControllerMap.set(vendorName, parsedNode); } } return {}; }, priority: 5, }, ], additionalRelationships: [ FairSquareSemanticEdgeType.FS_INJECTS as any, FairSquareSemanticEdgeType.FS_PROTECTED_BY as any, ], neo4j: { additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.BUSINESS_LOGIC], primaryLabel: FairSquareSemanticNodeType.FS_CONTROLLER, }, }, // FairSquare Service fairsquareService: { name: 'FairSquare Service', targetCoreType: CoreNodeType.CLASS_DECLARATION, semanticType: FairSquareSemanticNodeType.FS_SERVICE as any, priority: 90, detectionPatterns: [ { type: 'classname', pattern: /Service$/, confidence: 0.8, priority: 5, }, { type: 'decorator', pattern: 'Injectable', confidence: 0.9, priority: 8, }, { type: 'function', pattern: (parsedNode: ParsedNode) => { const node = parsedNode.sourceNode; if (!node || !Node.isClassDeclaration(node)) return false; const name = node.getName() ?? ''; const hasInjectable = node.getDecorators().some((d) => d.getName() === 'Injectable'); return name.endsWith('Service') && hasInjectable; }, confidence: 1.0, priority: 10, }, ], contextExtractors: [ { nodeType: CoreNodeType.CLASS_DECLARATION, extractor: extractInjectableDependencies, priority: 10, }, ], additionalRelationships: [FairSquareSemanticEdgeType.FS_INJECTS as any], neo4j: { additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.BUSINESS_LOGIC], primaryLabel: FairSquareSemanticNodeType.FS_SERVICE, }, }, // FairSquare Repository fairsquareRepository: { name: 'FairSquare Repository', targetCoreType: CoreNodeType.CLASS_DECLARATION, semanticType: FairSquareSemanticNodeType.FS_REPOSITORY as any, priority: 95, detectionPatterns: [ { type: 'classname', pattern: /Repository$/, confidence: 0.7, priority: 5, }, { type: 'function', pattern: (parsedNode: ParsedNode) => { const node = parsedNode.sourceNode; if (!node || !Node.isClassDeclaration(node)) return false; const baseClass = node.getExtends(); return baseClass?.getText() === 'Repository'; }, confidence: 1.0, priority: 10, }, ], contextExtractors: [ { nodeType: CoreNodeType.CLASS_DECLARATION, extractor: extractInjectableDependencies, priority: 10, }, { nodeType: CoreNodeType.CLASS_DECLARATION, extractor: extractRepositoryDals, priority: 9, }, ], additionalRelationships: [FairSquareSemanticEdgeType.FS_REPOSITORY_USES_DAL as any], neo4j: { additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.DATA_ACCESS], primaryLabel: FairSquareSemanticNodeType.FS_REPOSITORY, }, }, // FairSquare DAL (Data Access Layer) fairsquareDAL: { name: 'FairSquare DAL', targetCoreType: CoreNodeType.CLASS_DECLARATION, semanticType: FairSquareSemanticNodeType.FS_DAL as any, priority: 85, detectionPatterns: [ { type: 'classname', pattern: /DAL$/, confidence: 1.0, priority: 10, }, ], contextExtractors: [], additionalRelationships: [], neo4j: { additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.DATA_ACCESS, FairSquareLabel.DATABASE], primaryLabel: FairSquareSemanticNodeType.FS_DAL, }, }, // FairSquare Permission Manager fairsquarePermissionManager: { name: 'FairSquare Permission Manager', targetCoreType: CoreNodeType.CLASS_DECLARATION, semanticType: FairSquareSemanticNodeType.FS_PERMISSION_MANAGER as any, priority: 80, detectionPatterns: [ { type: 'classname', pattern: /PermissionManager$/, confidence: 1.0, priority: 10, }, ], contextExtractors: [], additionalRelationships: [], neo4j: { additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.SECURITY], primaryLabel: FairSquareSemanticNodeType.FS_PERMISSION_MANAGER, }, }, // FairSquare Vendor Client fairsquareVendorClient: { name: 'FairSquare Vendor Client', targetCoreType: CoreNodeType.CLASS_DECLARATION, semanticType: FairSquareSemanticNodeType.FS_VENDOR_CLIENT as any, priority: 75, detectionPatterns: [ { type: 'classname', pattern: /Client$/, confidence: 0.6, priority: 3, }, { type: 'filename', pattern: /vendor-client/, confidence: 0.9, priority: 8, }, { type: 'function', pattern: (parsedNode: ParsedNode) => { const node = parsedNode.sourceNode; if (!node || !Node.isClassDeclaration(node)) return false; const filePath = node.getSourceFile().getFilePath(); return filePath.includes('vendor-client') || filePath.includes('component-vendor'); }, confidence: 1.0, priority: 10, }, ], contextExtractors: [ { nodeType: CoreNodeType.CLASS_DECLARATION, extractor: extractMonorepoProject, priority: 10, }, ], additionalRelationships: [], neo4j: { additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.EXTERNAL_INTEGRATION], primaryLabel: FairSquareSemanticNodeType.FS_VENDOR_CLIENT, }, }, // FairSquare Route Definition fairsquareRouteDefinition: { name: 'FairSquare Route Definition', targetCoreType: CoreNodeType.VARIABLE_DECLARATION, semanticType: FairSquareSemanticNodeType.FS_ROUTE_DEFINITION as any, priority: 110, detectionPatterns: [ { type: 'function', pattern: (parsedNode: ParsedNode) => { const node = parsedNode.sourceNode; if (!node || !Node.isVariableDeclaration(node)) return false; const name = node.getName(); const typeNode = node.getTypeNode(); // Check if variable name ends with "Routes" AND has type ModuleRoute[] return !!name.endsWith('Routes') && !!typeNode?.getText().includes('ModuleRoute'); }, confidence: 1.0, priority: 10, }, ], contextExtractors: [ { nodeType: CoreNodeType.VARIABLE_DECLARATION, extractor: extractRouteDefinitions, priority: 10, }, ], additionalRelationships: [ FairSquareSemanticEdgeType.FS_ROUTES_TO as any, FairSquareSemanticEdgeType.FS_ROUTES_TO_HANDLER as any, ], neo4j: { additionalLabels: [FairSquareLabel.FAIRSQUARE], primaryLabel: FairSquareSemanticNodeType.FS_ROUTE_DEFINITION, }, }, // HTTP Endpoint (Controller methods) // fairsquareHttpEndpoint: { // name: 'FairSquare HTTP Endpoint', // targetCoreType: CoreNodeType.METHOD_DECLARATION, // semanticType: FairSquareSemanticNodeType.FS_HTTP_ENDPOINT as any, // priority: 100, // // detectionPatterns: [ // { // type: 'function', // pattern: (node: Node) => { // if (!Node.isMethodDeclaration(node)) return false; // const methodName = node.getName().toLowerCase(); // const httpMethods = ['get', 'post', 'put', 'delete', 'patch']; // // // Check if method is HTTP verb AND parent is Controller // const parent = node.getParent(); // const isController = // Node.isClassDeclaration(parent) && // (parent.getName()?.endsWith('Controller') || parent.getExtends()?.getText() === 'Controller'); // // return httpMethods.includes(methodName) && isController; // }, // confidence: 1.0, // priority: 10, // }, // ], // // contextExtractors: [ // { // nodeType: CoreNodeType.METHOD_DECLARATION, // extractor: extractHttpEndpoint, // priority: 10, // }, // ], // // additionalRelationships: [FairSquareSemanticEdgeType.FS_EXPOSES_HTTP as any], // // neo4j: { // additionalLabels: ['FairSquare', 'HttpEndpoint', 'API'], // primaryLabel: 'FairSquareHttpEndpoint', // }, // }, }, // ============================================================================ // EDGE ENHANCEMENTS (Relationship detection) // ============================================================================ edgeEnhancements: { // @Injectable([Dep1, Dep2]) creates INJECTS edges injectableDependencies: { name: 'Injectable Dependencies', semanticType: FairSquareSemanticEdgeType.FS_INJECTS as any, relationshipWeight: 0.95, // Critical - FairSquare DI is core architecture detectionPattern: (parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext) => { // FILTER: Only create INJECTS edges between ClassDeclarations if ( parsedSourceNode.coreType !== CoreNodeType.CLASS_DECLARATION || parsedTargetNode.coreType !== CoreNodeType.CLASS_DECLARATION ) { return false; } // Source has @Injectable([Target]) const sourceContext = parsedSourceNode.properties.context; const targetName = parsedTargetNode.properties.name; if (!sourceContext?.dependencies) return false; // Use exact match to avoid false positives from substring matching return sourceContext.dependencies.some((dep: string) => { // Remove quotes and whitespace from dependency string const cleanDep = dep.replace(/['"]/g, '').trim(); return cleanDep === targetName; }); }, contextExtractor: ( parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext, ) => ({ injectionType: 'constructor', framework: 'fairsquare', targetDependency: parsedTargetNode.properties.name, }), neo4j: { relationshipType: 'INJECTS', direction: 'OUTGOING', }, }, // Repository uses DAL repositoryUsesDAL: { name: 'Repository Uses DAL', semanticType: FairSquareSemanticEdgeType.FS_REPOSITORY_USES_DAL as any, relationshipWeight: 0.85, // High - data access layer relationships detectionPattern: (parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext) => { const isSourceRepo = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_REPOSITORY; const isTargetDAL = parsedTargetNode.semanticType === FairSquareSemanticNodeType.FS_DAL; if (!isSourceRepo || !isTargetDAL) return false; // Check if Repository injects this DAL const sourceDals = parsedSourceNode.properties.context?.dals ?? []; const targetName = parsedTargetNode.properties.name; // Use exact match to avoid false positives return sourceDals.some((dal: string) => { const cleanDal = dal.replace(/['"]/g, '').trim(); return cleanDal === targetName; }); }, contextExtractor: ( parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext, ) => ({ dalName: parsedTargetNode.properties.name, repositoryName: parsedSourceNode.properties.name, }), neo4j: { relationshipType: 'USES_DAL', direction: 'OUTGOING', }, }, // Controller uses PermissionManager controllerProtectedBy: { name: 'Controller Protected By Permission Manager', semanticType: FairSquareSemanticEdgeType.FS_PROTECTED_BY as any, relationshipWeight: 0.88, // High - security/authorization is critical detectionPattern: (parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext) => { const isSourceController = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_CONTROLLER; const isTargetPermissionManager = parsedTargetNode.semanticType === FairSquareSemanticNodeType.FS_PERMISSION_MANAGER; if (!isSourceController || !isTargetPermissionManager) return false; const sourcePermManager = parsedSourceNode.properties.context?.permissionManager; const targetName = parsedTargetNode.properties.name; if (!sourcePermManager) return false; // Use exact match to avoid false positives const cleanPermManager = sourcePermManager.replace(/['"]/g, '').trim(); return cleanPermManager === targetName; }, contextExtractor: ( parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext, ) => ({ permissionManagerName: parsedTargetNode.properties.name, controllerName: parsedSourceNode.properties.name, }), neo4j: { relationshipType: 'PROTECTED_BY', direction: 'OUTGOING', }, }, // Route definition routes to Controller routeToController: { name: 'Route To Controller', semanticType: FairSquareSemanticEdgeType.FS_ROUTES_TO as any, relationshipWeight: 0.92, // Critical - HTTP routing is primary entry point detectionPattern: (parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext) => { const isSourceRoute = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_ROUTE_DEFINITION; const isTargetController = parsedTargetNode.semanticType === FairSquareSemanticNodeType.FS_CONTROLLER; if (!isSourceRoute || !isTargetController) return false; // Check if any route in the definition references this controller const routes = parsedSourceNode.properties.context?.routes ?? []; const targetName = parsedTargetNode.properties.name; return routes.some((route: any) => route.controllerName === targetName); }, contextExtractor: (parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext) => { const routes = parsedSourceNode.properties.context?.routes ?? []; const targetName = parsedTargetNode.properties.name; const relevantRoutes = routes.filter((r: any) => r.controllerName === targetName); return { routeCount: relevantRoutes.length, routes: relevantRoutes, methods: relevantRoutes.map((r: any) => r.method), paths: relevantRoutes.map((r: any) => r.path), routeFile: parsedSourceNode.properties.context?.fileName, }; }, neo4j: { relationshipType: 'ROUTES_TO', direction: 'OUTGOING', }, }, // Route definition routes to Handler method routeToHandlerMethod: { name: 'Route To Handler Method', semanticType: FairSquareSemanticEdgeType.FS_ROUTES_TO_HANDLER as any, relationshipWeight: 0.9, // Critical - direct route to handler method detectionPattern: (parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext) => { const isSourceRoute = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_ROUTE_DEFINITION; const isTargetMethod = parsedTargetNode.coreType === CoreNodeType.METHOD_DECLARATION; if (!isSourceRoute || !isTargetMethod) return false; // Check if any route in the definition references this method as handler const routes = parsedSourceNode.properties.context?.routes ?? []; const targetMethodName = parsedTargetNode.properties.name; // Find routes that match this method name const matchingRoutes = routes.filter((route: any) => route.handler === targetMethodName); if (matchingRoutes.length === 0) return false; // CRITICAL FIX: Verify the method belongs to the correct controller // Find the parent class of this method by checking the AST node const targetNode = parsedTargetNode.sourceNode; if (!targetNode || !Node.isMethodDeclaration(targetNode)) return false; const parentClass = targetNode.getParent(); if (!parentClass || !Node.isClassDeclaration(parentClass)) return false; const parentClassName = parentClass.getName(); if (!parentClassName) return false; // Check if any matching route's controller name matches the parent class const isHandler = matchingRoutes.some((route: any) => route.controllerName === parentClassName); // If this method is a route handler AND is public, add HttpEndpoint label to the target node if (isHandler) { // Only add HttpEndpoint label to public methods (not private/protected) const isPublicMethod = parsedTargetNode.properties.visibility === 'public'; if ( isPublicMethod && !parsedTargetNode.labels.includes(FairSquareLabel.HTTP_ENDPOINT) && parsedTargetNode.properties ) { parsedTargetNode.labels.push(FairSquareLabel.HTTP_ENDPOINT); } } return isHandler; }, contextExtractor: (parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext) => { const routes = parsedSourceNode.properties.context?.routes ?? []; const targetMethodName = parsedTargetNode.properties.name; const matchingRoute = routes.find((r: any) => r.handler === targetMethodName); return { method: matchingRoute?.method, path: matchingRoute?.path, authenticated: matchingRoute?.authenticated, handler: targetMethodName, controllerName: matchingRoute?.controllerName, routeFile: parsedSourceNode.properties.context?.fileName, }; }, neo4j: { relationshipType: 'ROUTES_TO_HANDLER', direction: 'OUTGOING', }, }, internalApiCall: { name: 'Internal API Call', semanticType: FairSquareSemanticEdgeType.FS_INTERNAL_API_CALL as any, relationshipWeight: 0.82, // High - internal service communication detectionPattern: (parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext) => { // Service → VendorController (through VendorClient) const isSourceService = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_SERVICE; const isTargetController = parsedTargetNode.semanticType === FairSquareSemanticNodeType.FS_CONTROLLER; if (!isSourceService || !isTargetController) return false; // Get vendor controller map const vendorControllerMap = sharedContext?.get('vendorControllers') as Map<string, ParsedNode>; if (!vendorControllerMap) return false; // Check if target is a vendor controller let vendorName = ''; for (const [name, controllerNode] of vendorControllerMap) { if (controllerNode.id === parsedTargetNode.id) { vendorName = name; break; } } if (!vendorName) return false; // Check if service uses the corresponding VendorClient const sourceNode = parsedSourceNode.sourceNode; if (!sourceNode || !Node.isClassDeclaration(sourceNode)) return false; const expectedClientName = `${vendorName.charAt(0).toUpperCase() + vendorName.slice(1)}Client`; const properties = sourceNode.getProperties(); for (const prop of properties) { const typeNode = prop.getTypeNode(); if (typeNode?.getText() === expectedClientName) { return true; } const initializer = prop.getInitializer(); if (initializer && Node.isNewExpression(initializer)) { if (initializer.getExpression().getText() === expectedClientName) { return true; } } } return false; }, contextExtractor: (parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext) => { const vendorControllerMap = sharedContext?.get('vendorControllers') as Map<string, ParsedNode>; let vendorName = ''; for (const [name, controllerNode] of vendorControllerMap!) { if (controllerNode.id === parsedTargetNode.id) { vendorName = name; break; } } return { serviceName: parsedSourceNode.properties.name, vendorController: parsedTargetNode.properties.name, vendorClient: `${vendorName.charAt(0).toUpperCase() + vendorName.slice(1)}Client`, }; }, neo4j: { relationshipType: 'INTERNAL_API_CALL', direction: 'OUTGOING', }, }, usesRepository: { name: 'Uses Repository', semanticType: 'USES_REPOSITORY', relationshipWeight: 0.8, // High - service to repository data access detectionPattern: (parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext) => { // Service → Repository const isSourceService = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_SERVICE; const isTargetRepository = parsedTargetNode.semanticType === FairSquareSemanticNodeType.FS_REPOSITORY; if (!isSourceService || !isTargetRepository) return false; // Check if Service injects this Repository const sourceDependencies = parsedSourceNode.properties.context?.dependencies ?? []; const targetName = parsedTargetNode.properties.name; // Use exact match to avoid false positives return sourceDependencies.some((dep: string) => { const cleanDep = dep.replace(/['"]/g, '').trim(); return cleanDep === targetName; }); }, contextExtractor: ( parsedSourceNode: ParsedNode, parsedTargetNode: ParsedNode, allParsedNodes, sharedContext, ) => ({ repositoryName: parsedTargetNode.properties.name, serviceName: parsedSourceNode.properties.name, }), neo4j: { relationshipType: 'USES_REPOSITORY', direction: 'OUTGOING', }, }, }, };

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/drewdrewH/code-graph-context'

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