Skip to main content
Glama
component-crud.repository.ts8.3 kB
import { Component, ComponentInput, ComponentStatus } from '../../types'; import { BaseComponentRepository } from '../base/base-component.repository'; /** * Repository for basic Component CRUD operations * Handles create, read, update, delete operations for components */ export class ComponentCrudRepository extends BaseComponentRepository { /** * Get all active components for a specific repository and branch */ async getActiveComponents( repositoryNodeId: string, componentBranch: string, ): Promise<Component[]> { const query = ` MATCH (r:Repository {id: $repositoryNodeId})<-[:PART_OF]-(c:Component) WHERE c.status = $status AND c.branch = $componentBranch RETURN c ORDER BY c.name ASC `; const params = { repositoryNodeId, status: 'active', componentBranch }; try { const result = await this.executeQueryWithLogging(query, params, 'getActiveComponents'); if (result.length === 0) { return []; } const repoNameFromNodeId = repositoryNodeId.split(':')[0]; return result.map((row: any) => this.formatKuzuRowToComponent(row.c, repoNameFromNodeId, componentBranch), ); } catch (error: any) { this.logger.error( `Error in getActiveComponents for ${repositoryNodeId}, branch ${componentBranch}:`, error, ); return []; } } /** * Find a component by its logical ID and branch, within a given repository context */ async findByIdAndBranch( repositoryName: string, itemId: string, itemBranch: string, ): Promise<Component | null> { const graphUniqueId = this.createGraphUniqueId(repositoryName, itemBranch, itemId); try { // Step 1: Get the basic component info const query = `MATCH (c:Component {graph_unique_id: $graphUniqueId}) RETURN c LIMIT 1`; const result = await this.executeQueryWithLogging( query, { graphUniqueId }, 'findByIdAndBranch', ); if (result.length === 0) { this.logger.debug(`Component not found for GID: ${graphUniqueId}`); return null; } const componentNode = result[0]?.c; if (!componentNode) { this.logger.debug(`Component result format invalid for GID: ${graphUniqueId}`); return null; } // Get component data from the basic result const componentData = this.formatKuzuRowToComponent( componentNode, repositoryName, itemBranch, ); // Step 2: Get the component's dependencies const depsQuery = ` MATCH (c:Component {graph_unique_id: $graphUniqueId})-[:DEPENDS_ON]->(dep:Component) RETURN dep.id as depId `; const depsResult = await this.executeQueryWithLogging( depsQuery, { graphUniqueId }, 'findByIdAndBranch-dependencies', ); // Update the dependencies componentData.depends_on = depsResult.length > 0 ? depsResult.map((dep: any) => dep.depId) : []; this.logger.debug(`Found ${depsResult.length} dependencies for ${graphUniqueId}`); return componentData; } catch (error: any) { this.logger.error(`Error in findByIdAndBranch for GID ${graphUniqueId}:`, error); return null; } } /** * Update component status */ async updateComponentStatus( repositoryName: string, itemId: string, branch: string, status: ComponentStatus, ): Promise<Component | null> { const graphUniqueId = this.createGraphUniqueId(repositoryName, branch, itemId); const now = new Date(); const query = ` MATCH (c:Component {graph_unique_id: $graphUniqueId}) SET c.status = $status, c.updated_at = $updatedAt RETURN c `; const params = { graphUniqueId, status, updatedAt: now, }; try { const result = await this.executeQueryWithLogging(query, params, 'updateComponentStatus'); if (result.length > 0 && result[0].c) { this.logger.info(`Status updated for component ${graphUniqueId} to ${status}`); return this.formatKuzuRowToComponent(result[0].c, repositoryName, branch); } else { this.logger.warn( `Component ${graphUniqueId} not found or status update failed to return node.`, ); return this.findByIdAndBranch(repositoryName, itemId, branch); } } catch (error: any) { this.logger.error( `Error executing updateComponentStatus for ${graphUniqueId} to status ${status}:`, error, ); throw error; } } /** * Upsert a component with basic information */ async upsertComponent( repositoryNodeId: string, component: ComponentInput, ): Promise<Component | null> { const logicalRepositoryName = this.validateRepositoryNodeId( repositoryNodeId, 'upsertComponent', ); const componentId = String(component.id); const componentBranch = String(component.branch || 'main'); const graphUniqueId = this.createGraphUniqueId( logicalRepositoryName, componentBranch, componentId, ); const now = new Date(); try { const result = await this.kuzuClient.transaction(async (tx) => { // Atomic MERGE query that includes PART_OF relationship creation const upsertNodeQuery = ` MERGE (repo:Repository {id: $repositoryId}) ON CREATE SET repo.name = $repository, repo.created_at = $now MERGE (c:Component {id: $componentId, graph_unique_id: $graphUniqueId}) ON CREATE SET c.id = $componentId, c.name = $name, c.kind = $kind, c.status = $status, c.branch = $branch, c.repository = $repository, c.created_at = $createdAt, c.updated_at = $now ON MATCH SET c.id = $componentId, c.name = $name, c.kind = $kind, c.status = $status, c.branch = $branch, c.repository = $repository, c.created_at = $createdAt, c.updated_at = $now MERGE (c)-[:PART_OF]->(repo) `; await tx.executeQuery(upsertNodeQuery, { graphUniqueId, componentId, name: component.name, kind: component.kind || 'Unknown', status: component.status || 'active', branch: componentBranch, repository: logicalRepositoryName, repositoryId: repositoryNodeId, now: now, createdAt: component.created_at ? new Date(component.created_at) : now, }); // Handle dependencies if (component.depends_on && component.depends_on.length > 0) { // First, delete existing dependencies for this component await tx.executeQuery( 'MATCH (c:Component {graph_unique_id: $graphUniqueId})-[r:DEPENDS_ON]->() DELETE r', { graphUniqueId }, ); for (const depId of component.depends_on) { const depGraphUniqueId = this.createGraphUniqueId( logicalRepositoryName, componentBranch, depId, ); // Ensure dependency node exists (as a placeholder if needed) await tx.executeQuery( `MERGE (dep:Component {graph_unique_id: $depGraphUniqueId}) ON CREATE SET dep.id = $depId, dep.name = $depName, dep.status = 'planned', dep.branch = $branch`, { depGraphUniqueId, depId, depName: `Placeholder for ${depId}`, branch: componentBranch, }, ); // Create the new dependency relationship await tx.executeQuery( 'MATCH (c:Component {graph_unique_id: $cId}), (d:Component {graph_unique_id: $dId}) MERGE (c)-[:DEPENDS_ON]->(d)', { cId: graphUniqueId, dId: depGraphUniqueId }, ); } } return true; }); if (result) { return this.findByIdAndBranch(logicalRepositoryName, componentId, componentBranch); } return null; } catch (error: any) { this.logger.error(`ERROR in upsertComponent for ${graphUniqueId}:`, error); throw error; } } }

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/Jakedismo/KuzuMem-MCP'

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