@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
JavaScript
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();
}
}
}