@memory-bank/mcp
Version:
Memory-enabled Co-Pilot (MCP) server for managing project documentation and context
313 lines • 11 kB
JavaScript
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