Skip to main content
Glama
decision.repository.ts7.39 kB
import { KuzuDBClient } from '../db/kuzu'; import { Decision } from '../types'; import { formatGraphUniqueId } from '../utils/id.utils'; import { loggers } from '../utils/logger'; import { RepositoryRepository } from './repository.repository'; /** * Repository for Decision, using KuzuDB and Cypher queries. * Each instance is now tied to a specific KuzuDBClient. */ export class DecisionRepository { private logger = loggers.repository(); private kuzuClient: KuzuDBClient; private repositoryRepo: RepositoryRepository; /** * Constructor requires an initialized KuzuDBClient instance. * @param kuzuClient An initialized KuzuDBClient. */ public constructor(kuzuClient: KuzuDBClient, repositoryRepo: RepositoryRepository) { if (!kuzuClient) { throw new Error('DecisionRepository requires an initialized KuzuDBClient instance.'); } if (!repositoryRepo) { throw new Error('DecisionRepository requires an initialized RepositoryRepository instance.'); } this.kuzuClient = kuzuClient; this.repositoryRepo = repositoryRepo; } private formatKuzuRowToDecision( kuzuRowData: any, repositoryName: string, branch: string, ): Decision { const rawDecision = kuzuRowData.properties || kuzuRowData; const logicalId = rawDecision.id?.toString(); const graphUniqueId = rawDecision.graph_unique_id?.toString() || formatGraphUniqueId(repositoryName, branch, logicalId); let decisionDate = rawDecision.dateCreated || rawDecision.date; if (decisionDate instanceof Date) { decisionDate = decisionDate.toISOString().split('T')[0]; } else if (typeof decisionDate === 'number') { decisionDate = new Date(decisionDate).toISOString().split('T')[0]; } else if ( typeof decisionDate === 'object' && decisionDate !== null && 'year' in decisionDate && 'month' in decisionDate && 'day' in decisionDate ) { decisionDate = `${String(decisionDate.year).padStart(4, '0')}-${String(decisionDate.month).padStart(2, '0')}-${String(decisionDate.day).padStart(2, '0')}`; } return { id: logicalId, graph_unique_id: graphUniqueId, name: rawDecision.title || rawDecision.name, context: rawDecision.rationale || rawDecision.context, date: decisionDate, branch, repository: `${repositoryName}:${branch}`, status: rawDecision.status, created_at: rawDecision.created_at ? new Date(rawDecision.created_at) : new Date(), updated_at: rawDecision.updated_at ? new Date(rawDecision.updated_at) : new Date(), } as Decision; } /** * Get all decisions for a repository node and branch in a date range. */ async getDecisionsByDateRange( repositoryNodeId: string, decisionBranch: string, startDate: string, endDate: string, ): Promise<Decision[]> { const query = ` MATCH (repo:Repository {id: $repositoryNodeId})<-[:PART_OF]-(d:Decision) WHERE d.branch = $decisionBranch AND d.date >= date($startDate) AND d.date <= date($endDate) RETURN d ORDER BY d.date DESC `; const params = { repositoryNodeId, decisionBranch, startDate, endDate }; try { const result = await this.kuzuClient.executeQuery(query, params); if (!result) { return []; } const repoNameFromNodeId = repositoryNodeId.split(':')[0]; return result.map((row: any) => this.formatKuzuRowToDecision(row.d, repoNameFromNodeId, decisionBranch), ); } catch (error) { this.logger.error( `[DecisionRepository] Error in getDecisionsByDateRange for ${repositoryNodeId}, branch ${decisionBranch}:`, error, ); return []; } } /** * Creates or updates a decision. * `decision.repository` is the Repository node PK (e.g., 'my-repo:main'). * `decision.branch` is the branch of this Decision entity. * `decision.id` is the logical ID of this Decision entity. */ async upsertDecision(decision: Decision): Promise<Decision | null> { const logger = console; // Placeholder logger const { repository: repositoryNodeId, branch, id: logicalId, name, date, context: decisionContext, status, } = decision; const [logicalRepositoryName] = repositoryNodeId.split(':'); const graphUniqueId = formatGraphUniqueId(logicalRepositoryName, branch, logicalId); const now = new Date(); const query = ` MERGE (d:Decision {id: $id, graph_unique_id: $graphUniqueId}) ON CREATE SET d.title = $name, d.dateCreated = $date, d.rationale = $context, d.status = $status, d.created_at = $now, d.updated_at = $now ON MATCH SET d.title = $name, d.dateCreated = $date, d.rationale = $context, d.status = $status, d.updated_at = $now RETURN d `; const params = { id: logicalId, graphUniqueId, name, date: new Date(date), context: decisionContext, status: status || 'proposed', now, }; try { const result = await this.kuzuClient.executeQuery(query, params); if (result && result.length > 0) { return this.formatKuzuRowToDecision(result[0].d, logicalRepositoryName, branch); } return null; } catch (error: any) { logger.error( `[DecisionRepository] Error in upsertDecision for GID ${graphUniqueId}: ${error.message}`, { stack: error.stack }, ); throw error; } } /** * Find a decision by its logical ID and branch, within a given repository name. */ async findByIdAndBranch( repositoryName: string, itemId: string, itemBranch: string, ): Promise<Decision | null> { const graphUniqueId = formatGraphUniqueId(repositoryName, itemBranch, itemId); const query = `MATCH (d:Decision {graph_unique_id: $graphUniqueId}) RETURN d LIMIT 1`; const params = { graphUniqueId }; try { const result = await this.kuzuClient.executeQuery(query, params); if (result && result.length > 0 && result[0].d) { return this.formatKuzuRowToDecision(result[0].d, repositoryName, itemBranch); } return null; } catch (error) { this.logger.error( `[DecisionRepository] Error in findByIdAndBranch for GID ${graphUniqueId}:`, error, ); return null; } } /** * Get all decisions for a repository node and branch. */ async getAllDecisions(repositoryNodeId: string, decisionBranch: string): Promise<Decision[]> { const query = ` MATCH (repo:Repository {id: $repositoryNodeId})<-[:PART_OF]-(d:Decision) WHERE d.branch = $decisionBranch RETURN d ORDER BY d.dateCreated DESC, d.title ASC `; const params = { repositoryNodeId, decisionBranch }; try { const result = await this.kuzuClient.executeQuery(query, params); if (!result) { return []; } const repoNameFromNodeId = repositoryNodeId.split(':')[0]; return result.map((row: any) => this.formatKuzuRowToDecision(row.d, repoNameFromNodeId, decisionBranch), ); } catch (error) { this.logger.error( `[DecisionRepository] Error in getAllDecisions for ${repositoryNodeId}, branch ${decisionBranch}:`, error, ); return []; } } }

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