UNPKG

@memory-bank/mcp

Version:

Memory-enabled Co-Pilot (MCP) server for managing project documentation and context

313 lines 11 kB
import { DocumentId } from './DocumentId.js'; import { Tag } from './Tag.js'; import { DocumentVersionInfo } from './DocumentVersionInfo.js'; import { DomainError, DomainErrorCodes } from '../../shared/errors/DomainError.js'; export const SCHEMA_VERSION = 'memory_document_v2'; /** * JsonDocument entity represents a structured document stored in JSON format * It uses the v2 schema with typed content based on document type */ export class JsonDocument { _id; _path; _title; _documentType; _tags; _content; _branch; _versionInfo; static validator; /** * Set the document validator to use for validation * This is injected from outside to avoid domain depending on infrastructure * @param validator Document validator to use */ static setValidator(validator) { JsonDocument.validator = validator; } /** * Get the current validator (throws if not set) */ static getValidator() { if (!JsonDocument.validator) { throw new DomainError(DomainErrorCodes.INITIALIZATION_ERROR, 'Document validator not set. Call JsonDocument.setValidator() before using JsonDocument.'); } return JsonDocument.validator; } constructor(_id, _path, _title, _documentType, _tags, _content, _branch, _versionInfo = new DocumentVersionInfo({ version: 1 })) { this._id = _id; this._path = _path; this._title = _title; this._documentType = _documentType; this._tags = _tags; this._content = _content; this._branch = _branch; this._versionInfo = _versionInfo; } /** * Parse a JSON string into a JsonDocument * @param jsonString Raw JSON string * @param path Document path * @returns JsonDocument instance * @throws DomainError if parsing fails or validation fails */ static fromString(jsonString, path) { try { const jsonData = JSON.parse(jsonString); return JsonDocument.fromObject(jsonData, path); } catch (error) { if (error instanceof DomainError) { throw error; } throw new DomainError(DomainErrorCodes.VALIDATION_ERROR, `Failed to parse JSON document: ${error.message}`); } } /** * Create a JsonDocument from a parsed JSON object * @param jsonData Parsed JSON object * @param path Document path * @returns JsonDocument instance * @throws DomainError if validation fails */ static fromObject(jsonData, path) { try { JsonDocument.getValidator().validateDocument(jsonData); } catch (error) { if (error instanceof DomainError) { throw error; } throw new DomainError(DomainErrorCodes.VALIDATION_ERROR, `Invalid JSON document structure: ${error.message}`); } const baseDocument = jsonData; // documentType はトップレベルから取得 const documentType = baseDocument.documentType; const metadata = baseDocument.metadata; // metadata を直接取得 // metadata が存在するかチェック if (!metadata) { throw new DomainError(DomainErrorCodes.VALIDATION_ERROR, 'Metadata is missing in the document object'); } const id = DocumentId.create(metadata.id); const tags = (metadata.tags || []).map((tag) => Tag.create(tag)); // tags が optional な場合も考慮 const lastModified = new Date(metadata.lastModified); const versionInfo = new DocumentVersionInfo({ version: metadata.version || 1, lastModified: lastModified, modifiedBy: 'system', // 必要に応じて変更 }); const branch = metadata.branch; // metadata から branch を取得 return new JsonDocument(id, path, metadata.title, documentType, tags, baseDocument.content || baseDocument, branch, versionInfo); } /** * Create a new JsonDocument with given values * @param params Document creation parameters * @returns JsonDocument instance */ static create({ id = DocumentId.generate(), path, title, documentType, tags = [], content, branch, versionInfo, }) { try { JsonDocument.getValidator().validateContent(documentType, content); } catch (error) { if (error instanceof DomainError) { throw error; } throw new DomainError(DomainErrorCodes.VALIDATION_ERROR, `Invalid content for ${documentType} document: ${error.message}`); } return new JsonDocument(id, path, title, documentType, [...tags], content, branch, versionInfo); } /** * Get the document ID */ get id() { return this._id; } /** * Get the document path */ get path() { return this._path; } /** * Get the document title */ get title() { return this._title; } /** * Get the document type */ get documentType() { return this._documentType; } /** * Set the document type (internal use only for testing) */ set documentType(type) { this._documentType = type; } /** * Get the document tags */ get tags() { return [...this._tags]; } /** * Get the document content */ get content() { return this._content; } /** * Get the branch name */ get branch() { return this._branch; } /** * Get the version info */ get versionInfo() { return this._versionInfo; } /** * Get the last modified date */ get lastModified() { return this._versionInfo.lastModified; } /** * Get the document version */ get version() { return this._versionInfo.version; } /** * Check if document has a specific tag * @param tag Tag to check * @returns boolean indicating if document has tag */ hasTag(tag) { return this._tags.some((t) => t.equals(tag)); } /** * Create a new document with updated path * @param path New path * @returns New JsonDocument instance */ updatePath(path) { if (path.equals(this._path)) { return this; } const updatedVersionInfo = this._versionInfo.nextVersion(); return new JsonDocument(this._id, path, this._title, this._documentType, this._tags, this._content, this._branch, updatedVersionInfo); } /** * Create a new document with updated title * @param title New title * @returns New JsonDocument instance */ updateTitle(title) { if (title === this._title) { return this; } const updatedVersionInfo = this._versionInfo.nextVersion(); return new JsonDocument(this._id, this._path, title, this._documentType, this._tags, this._content, this._branch, updatedVersionInfo); } /** * Create a new document with updated content * @param content New content * @returns New JsonDocument instance */ updateContent(content) { try { JsonDocument.getValidator().validateContent(this._documentType, content); } catch (error) { if (error instanceof DomainError) { throw error; } throw new DomainError(DomainErrorCodes.VALIDATION_ERROR, `Invalid content for ${this._documentType} document: ${error.message}`); } const updatedVersionInfo = this._versionInfo.nextVersion(); return new JsonDocument(this._id, this._path, this._title, this._documentType, this._tags, content, this._branch, updatedVersionInfo); } /** * Create a new document with added tag * @param tag Tag to add * @returns New JsonDocument instance */ addTag(tag) { if (this.hasTag(tag)) { return this; } const updatedVersionInfo = this._versionInfo.nextVersion(); return new JsonDocument(this._id, this._path, this._title, this._documentType, [...this._tags, tag], this._content, this._branch, updatedVersionInfo); } /** * Create a new document with removed tag * @param tag Tag to remove * @returns New JsonDocument instance */ removeTag(tag) { if (!this.hasTag(tag)) { return this; } const updatedVersionInfo = this._versionInfo.nextVersion(); return new JsonDocument(this._id, this._path, this._title, this._documentType, this._tags.filter((t) => !t.equals(tag)), this._content, this._branch, updatedVersionInfo); } /** * Create a new document with updated tags * @param tags New tags * @returns New JsonDocument instance */ updateTags(tags) { const updatedVersionInfo = this._versionInfo.nextVersion(); return new JsonDocument(this._id, this._path, this._title, this._documentType, [...tags], this._content, this._branch, updatedVersionInfo); } /** * Converts the document to a serializable object (BaseJsonDocumentV2) * @returns Document as a serializable object */ toObject() { // metadata オブジェクトから documentType を除外 const metadata = { id: this._id.value, title: this._title, // documentType: this._documentType, // metadata には含めない path: this._path.value, tags: this._tags.map((tag) => tag.value), lastModified: this._versionInfo.lastModified.toISOString(), // ISO 文字列に変換 createdAt: new Date().toISOString(), // createdAt は常に現在時刻? or VersionInfo から取得? 要確認 -> 一旦 new Date() version: this._versionInfo.version, }; // branch は optional なので存在する場合のみ追加 if (this._branch) { metadata.branch = this._branch; // 型アサーションで追加 } // documentType をトップレベルに含める return { schema: SCHEMA_VERSION, documentType: this._documentType, // documentType をトップレベルに metadata, content: this._content, }; } /** * Converts the document to a JSON string * @param pretty Whether to pretty-print the JSON (default: false) * @returns JSON string representation */ toString(pretty = false) { return JSON.stringify(this.toObject(), null, pretty ? 2 : undefined); } /** * Checks if two JsonDocument instances are equal (have the same ID) * @param other Another JsonDocument instance * @returns boolean indicating equality */ equals(other) { return this._id.equals(other._id); } } //# sourceMappingURL=JsonDocument.js.map