UNPKG

@shirokuma-library/mcp-knowledge-base

Version:

MCP server for AI-powered knowledge management with semantic search, graph analysis, and automatic enrichment

123 lines (122 loc) 4.59 kB
import { Keyword } from '../../entities/Keyword.js'; import { ItemKeyword } from '../../entities/ItemKeyword.js'; import { Concept } from '../../entities/Concept.js'; import { ItemConcept } from '../../entities/ItemConcept.js'; export class DataStorage { dataSource; constructor(dataSource) { this.dataSource = dataSource; } async storeKeywordsForItem(itemId, keywords) { const keywordRepo = this.dataSource.getRepository(Keyword); const itemKeywordRepo = this.dataSource.getRepository(ItemKeyword); for (const { keyword, weight } of keywords) { let keywordEntity = await keywordRepo.findOne({ where: { word: keyword } }); if (!keywordEntity) { keywordEntity = await keywordRepo.save({ word: keyword }); } const existing = await itemKeywordRepo.findOne({ where: { itemId, keywordId: keywordEntity.id } }); if (!existing) { await itemKeywordRepo.save({ itemId, keywordId: keywordEntity.id, weight }); } else { await itemKeywordRepo.update({ itemId, keywordId: keywordEntity.id }, { weight }); } } } async storeConceptsForItem(itemId, concepts) { const conceptRepo = this.dataSource.getRepository(Concept); const itemConceptRepo = this.dataSource.getRepository(ItemConcept); for (const { concept, confidence } of concepts) { let conceptEntity = await conceptRepo.findOne({ where: { name: concept } }); if (!conceptEntity) { conceptEntity = await conceptRepo.save({ name: concept }); } const existing = await itemConceptRepo.findOne({ where: { itemId, conceptId: conceptEntity.id } }); if (!existing) { await itemConceptRepo.save({ itemId, conceptId: conceptEntity.id, confidence }); } else { await itemConceptRepo.update({ itemId, conceptId: conceptEntity.id }, { confidence }); } } } async getKeywordsForItem(itemId) { const itemKeywordRepo = this.dataSource.getRepository(ItemKeyword); const relations = await itemKeywordRepo .createQueryBuilder('ik') .leftJoinAndSelect('ik.keyword', 'k') .where('ik.itemId = :itemId', { itemId }) .orderBy('ik.weight', 'DESC') .getMany(); return relations.map(r => ({ keyword: r.keyword.word, weight: r.weight })); } async getConceptsForItem(itemId) { const itemConceptRepo = this.dataSource.getRepository(ItemConcept); const relations = await itemConceptRepo .createQueryBuilder('ic') .leftJoinAndSelect('ic.concept', 'c') .where('ic.itemId = :itemId', { itemId }) .orderBy('ic.confidence', 'DESC') .getMany(); return relations.map(r => ({ concept: r.concept.name, confidence: r.confidence })); } async cleanupOrphaned() { const keywordIds = await this.dataSource .getRepository(ItemKeyword) .createQueryBuilder('ik') .select('DISTINCT ik.keywordId', 'keywordId') .getRawMany(); const usedKeywordIds = keywordIds.map(k => k.keywordId); if (usedKeywordIds.length > 0) { await this.dataSource .createQueryBuilder() .delete() .from(Keyword) .where('id NOT IN (:...ids)', { ids: usedKeywordIds }) .execute(); } const conceptIds = await this.dataSource .getRepository(ItemConcept) .createQueryBuilder('ic') .select('DISTINCT ic.conceptId', 'conceptId') .getRawMany(); const usedConceptIds = conceptIds.map(c => c.conceptId); if (usedConceptIds.length > 0) { await this.dataSource .createQueryBuilder() .delete() .from(Concept) .where('id NOT IN (:...ids)', { ids: usedConceptIds }) .execute(); } } }