@memory-bank/mcp
Version:
Memory-enabled Co-Pilot (MCP) server for managing project documentation and context
369 lines • 15.1 kB
JavaScript
/**
* JsonTemplateLoaderRepository
*
* TypeScript定義からテンプレートを直接ロードするリポジトリの実装。
* ITemplateRepositoryインターフェースとITemplateLoaderインターフェースの両方を実装し、
* FileTemplateRepositoryとTemplateRendererの機能を統合した効率的で軽量な実装です。
*/
import { Template as DomainTemplate } from '../../domain/templates/Template.js';
import { Section } from '../../domain/templates/Section.js';
import * as templateDefinitions from '../../templates/definitions/index.js';
import { logger } from '../../shared/utils/logger.js';
/**
* JsonTemplateLoaderRepository class
* TypeScript定義からテンプレートを直接ロードする
* ITemplateRepositoryとITemplateLoaderの両方を実装したクラス
*/
export class JsonTemplateLoaderRepository {
i18nProvider;
// テンプレートをキャッシュするためのマップ
templateCache = new Map();
cacheDirty = true;
componentLogger = logger.withContext({ component: 'JsonTemplateLoaderRepository' });
/**
* コンストラクタ
* @param i18nProvider 国際化サービス
*/
constructor(i18nProvider) {
this.i18nProvider = i18nProvider;
}
/**
* テンプレートIDをTypeScript変数名に変換
* 例: 'rules' → 'rulesTemplate'
* 例: 'active-context' → 'activeContextTemplate'
*/
getTypeScriptTemplateName(templateId) {
return `${templateId.replace(/-([a-z])/g, (_, p1) => p1.toUpperCase())}Template`;
}
/**
* 初期化メソッド - 必要に応じて呼び出す
*/
async initialize() {
await this.loadAllTemplates();
}
/**
* すべてのテンプレートをロードしてキャッシュに格納
*/
async loadAllTemplates() {
try {
// キャッシュをクリア
this.templateCache.clear();
this.componentLogger.debug('Loading templates from TypeScript definitions');
// テンプレートをロード
for (const [key, value] of Object.entries(templateDefinitions)) {
if (key.endsWith('Template') && value && typeof value === 'object' && 'schema' in value && value.schema === 'template_v1') {
try {
const tsTemplate = value;
if (tsTemplate.metadata && tsTemplate.content && tsTemplate.metadata.id) {
const id = tsTemplate.metadata.id;
this.componentLogger.debug(`Loading template ${id} from TypeScript definition`);
// テンプレートを作成
const template = this.convertSchemaTemplateToDomain(tsTemplate);
// キャッシュに追加
this.templateCache.set(id, template);
}
}
catch (error) {
this.componentLogger.warn(`Failed to load template ${key} from TypeScript:`, error);
}
}
}
this.componentLogger.info(`Loaded ${this.templateCache.size} templates from TypeScript definitions`);
this.cacheDirty = false;
}
catch (error) {
this.componentLogger.error('Failed to load templates:', error);
throw new Error(`Failed to load templates: ${error.message}`);
}
}
/**
* スキーマテンプレートをドメインテンプレートに変換
*/
convertSchemaTemplateToDomain(tsTemplate) {
// セクションを作成
const sections = tsTemplate.content.sections.map(sectionData => {
// このセクションのタイトルとコンテンツマップを作成
const titleMap = {};
const contentMap = {};
if (this.i18nProvider) {
// 英語のタイトルとコンテンツ
titleMap.en = this.i18nProvider.translate({
key: sectionData.titleKey,
language: 'en'
});
contentMap.en = this.i18nProvider.translate({
key: sectionData.contentKey,
language: 'en'
});
// 日本語のタイトルとコンテンツ
titleMap.ja = this.i18nProvider.translate({
key: sectionData.titleKey,
language: 'ja'
});
contentMap.ja = this.i18nProvider.translate({
key: sectionData.contentKey,
language: 'ja'
});
// 中国語のタイトルとコンテンツ
titleMap.zh = this.i18nProvider.translate({
key: sectionData.titleKey,
language: 'zh'
});
contentMap.zh = this.i18nProvider.translate({
key: sectionData.contentKey,
language: 'zh'
});
}
else {
// フォールバック
titleMap.en = sectionData.titleKey;
contentMap.en = sectionData.contentKey;
}
return new Section(sectionData.id, titleMap, contentMap, sectionData.isOptional);
});
// 名前マップを作成
const nameMap = {};
if (this.i18nProvider && tsTemplate.metadata.titleKey) {
nameMap.en = this.i18nProvider.translate({
key: tsTemplate.metadata.titleKey,
language: 'en'
});
nameMap.ja = this.i18nProvider.translate({
key: tsTemplate.metadata.titleKey,
language: 'ja'
});
nameMap.zh = this.i18nProvider.translate({
key: tsTemplate.metadata.titleKey,
language: 'zh'
});
}
else {
nameMap.en = tsTemplate.metadata.titleKey || tsTemplate.metadata.id;
}
// ドメインテンプレートを作成
return new DomainTemplate(tsTemplate.metadata.id, tsTemplate.metadata.type, nameMap, sections);
}
/**
* テンプレートを取得する
*/
async getTemplate(id) {
if (this.cacheDirty) {
await this.loadAllTemplates();
}
return this.templateCache.get(id) || null;
}
/**
* テンプレートをJSONオブジェクトとして取得する
*/
async getTemplateAsJsonObject(id, language, _variables) {
this.componentLogger.debug(`Getting template '${id}' as JSON object for language: ${language.code}`);
// キャッシュが汚れている場合はリロード
if (this.cacheDirty) {
await this.loadAllTemplates();
}
// キャッシュからテンプレートを取得
let template = this.templateCache.get(id);
// 見つからない場合は、再度リロードして確認
if (!template) {
this.componentLogger.debug(`Template '${id}' not found in cache, forcing reload`);
this.cacheDirty = true;
await this.loadAllTemplates();
template = this.templateCache.get(id);
}
// TypeScript定義から直接ロード
if (!template) {
this.componentLogger.debug(`Template '${id}' still not found, attempting to load directly from TypeScript`);
const tsTemplateName = this.getTypeScriptTemplateName(id);
const tsTemplate = templateDefinitions[tsTemplateName];
if (tsTemplate && tsTemplate.schema === 'template_v1') {
this.componentLogger.debug(`Found TypeScript template '${id}' as '${tsTemplateName}'`);
try {
template = this.convertSchemaTemplateToDomain(tsTemplate);
// キャッシュに保存
this.templateCache.set(id, template);
}
catch (error) {
this.componentLogger.error(`Failed to process TypeScript template '${id}':`, error);
}
}
}
if (!template) {
throw new Error(`Template not found: ${id}`);
}
// JSONオブジェクトを構築
const jsonObject = {
id: template.id,
type: template.type,
name: template.getName(language),
sections: template.sections.map(section => ({
id: section.id,
title: section.getTitle(language),
content: section.getContent(language),
isOptional: section.isOptional,
})),
};
return jsonObject;
}
/**
* 指定したタイプのテンプレートをすべて取得
*/
async getTemplatesByType(type) {
if (this.cacheDirty) {
await this.loadAllTemplates();
}
const templates = [];
for (const template of this.templateCache.values()) {
if (template.type === type) {
templates.push(template);
}
}
return templates;
}
/**
* テンプレートを保存する(TypeScript定義でTypeScriptファイルに書き込まれるため、このメソッドはサポートしない)
*/
async saveTemplate(_template) {
this.componentLogger.warn('saveTemplate is not supported in JsonTemplateLoaderRepository. Templates should be defined in TypeScript files.');
return false;
}
/**
* テンプレートが存在するかチェック
*/
async templateExists(id) {
if (this.cacheDirty) {
await this.loadAllTemplates();
}
if (this.templateCache.has(id)) {
return true;
}
// TypeScript定義内で直接チェック
const tsTemplateName = this.getTypeScriptTemplateName(id);
return !!templateDefinitions[tsTemplateName];
}
/**
* 利用可能なすべてのテンプレートIDを取得
*/
async getAllTemplateIds() {
if (this.cacheDirty) {
await this.loadAllTemplates();
}
return Array.from(this.templateCache.keys());
}
/**
* 利用可能なすべてのテンプレートタイプを取得
*/
async getAllTemplateTypes() {
if (this.cacheDirty) {
await this.loadAllTemplates();
}
const types = new Set();
for (const template of this.templateCache.values()) {
types.add(template.type);
}
return Array.from(types);
}
// ===== ITemplateLoader インターフェース実装 =====
/**
* TemplateLoaderインターフェースのloadJsonTemplateメソッドを実装
* スキーマTemplateを直接返す
*/
async loadJsonTemplate(templateId) {
this.componentLogger.debug(`Loading JSON template: ${templateId}`);
// TypeScript定義から直接テンプレートを取得
const tsTemplateName = this.getTypeScriptTemplateName(templateId);
const template = templateDefinitions[tsTemplateName];
if (template && template.schema === 'template_v1') {
this.componentLogger.debug(`Found TypeScript template '${templateId}' as '${tsTemplateName}'`);
return template;
}
// 見つからなかった場合はエラー
this.componentLogger.error(`Template '${templateId}' not found in TypeScript definitions`);
throw new Error(`Template not found: ${templateId}`);
}
/**
* TemplateLoaderインターフェースのgetMarkdownTemplateメソッドを実装
* テンプレートをMarkdownにレンダリング
*/
async getMarkdownTemplate(templateId, language, variables) {
this.componentLogger.debug(`Getting markdown template: ${templateId}, language: ${language}`);
// 言語サポートチェック
if (!this.i18nProvider.isLanguageSupported(language)) {
throw new Error(`Unsupported language: ${language}`);
}
try {
// TypeScript定義からテンプレートを取得
const tsTemplate = await this.loadJsonTemplate(templateId);
// Markdownにレンダリング
return this.renderTemplateToMarkdown(tsTemplate, language, variables);
}
catch (error) {
this.componentLogger.error(`Failed to load or render template ${templateId}`, { error });
throw error;
}
}
/**
* テンプレートをMarkdownにレンダリング
* 旧TemplateRendererの機能を統合
*/
renderTemplateToMarkdown(template, language, variables) {
const lines = [];
// タイトルを追加
const titleKey = template.metadata.titleKey;
const title = this.i18nProvider.translate({
key: titleKey,
language
});
lines.push(`# ${title}\n`);
// セクションを追加
for (const section of template.content.sections) {
const sectionContent = this.renderSection(section, language, variables);
// 空のオプションセクションはスキップ
if (section.isOptional && sectionContent.trim() === '') {
continue;
}
lines.push(sectionContent);
}
return lines.join('\n');
}
/**
* テンプレートのセクションをレンダリング
*/
renderSection(section, language, variables) {
const lines = [];
// コンテンツを先に翻訳して、空かどうかをチェック
let content = section.contentKey ? this.i18nProvider.translate({
key: section.contentKey,
language
}) : '';
if (variables && content) {
content = this.replaceVariables(content, variables);
}
// セクションがオプショナルで内容が空の場合は空文字を返す
if (section.isOptional && content.trim() === '') {
return '';
}
// 内容が空でない場合はタイトルを追加
const title = this.i18nProvider.translate({
key: section.titleKey,
language
});
if (title.trim() && content.trim() !== '') {
lines.push(`## ${title}\n`);
}
// 内容が空でない場合は追加
if (content.trim() !== '') {
lines.push(content);
}
return lines.join('\n');
}
/**
* テキスト内の変数を置換
*/
replaceVariables(text, variables) {
// 小文字とアンダースコアを含む変数名をサポート
return text.replace(/\{\{([a-zA-Z0-9_]+)\}\}/g, (match, name) => {
return variables[name] || match;
});
}
}
//# sourceMappingURL=JsonTemplateLoaderRepository.js.map