mcp-decisive
Version:
MCP server for WRAP decision-making framework with structured output
116 lines • 5.31 kB
JavaScript
import { ok, err } from 'neverthrow';
import { loadOptions } from '../../../effect/options-storage.js';
/**
* Options Read Model Implementation
*
* This module provides query functions for retrieving current options status.
* It integrates with the effect layer and transforms command model data
* into read-optimized views.
*/
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Data Transformation Functions
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/**
* Transform OptionList from command model to OptionsView for read model
*
* This function bridges the command and read sides, reusing the same value objects
* to maintain consistency while providing a read-optimized interface.
*/
const transformToOptionsView = (optionList) => {
try {
// The command model already uses the same value objects, so we can reuse them directly
const optionsView = {
options: optionList.options
};
return ok(optionsView);
}
catch (error) {
return err({
type: 'DataCorruption',
message: 'データの変換中にエラーが発生しました',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
};
/**
* Map FileSystemError to OptionsReadError
*
* Transforms effect layer errors into read model errors,
* handling the special case where file-not-found is not an error.
*/
const mapFileSystemError = (fsError) => {
// File not found is not an error - it means no options are defined yet
if (fsError.originalError && 'code' in fsError.originalError && fsError.originalError.code === 'ENOENT') {
return null; // This will be handled as "no options defined" case
}
return {
type: 'FileSystemError',
message: fsError.message,
originalError: fsError.originalError
};
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Query Functions - Public API
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/**
* Get Current Options Query
*
* Retrieves the current options from storage and transforms them into
* a read-optimized view. Returns null when no options are defined (not an error).
*
* @returns Promise<Result<OptionsView | null, OptionsReadError>>
* - Ok(OptionsView) when options exist
* - Ok(null) when no options are defined
* - Err(OptionsReadError) when actual errors occur (file system, data corruption)
*/
export const getCurrentOptions = async () => {
const loadResult = await loadOptions();
return loadResult.match(
// Success: Options exist
(optionList) => transformToOptionsView(optionList),
// Error: Handle different error types
(fsError) => {
const readError = mapFileSystemError(fsError);
// File not found means no options are defined - this is OK
if (readError === null) {
return ok(null);
}
// Other file system errors are actual errors
return err(readError);
});
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Utility Functions
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/**
* Convert OptionsView to serializable format for MCP responses
*
* This function converts the domain value objects to plain strings
* suitable for JSON serialization and MCP tool responses.
*/
export const serializeOptionsView = (optionsView) => {
if (!optionsView) {
throw new Error('serializeOptionsView: optionsView is required');
}
return {
options: optionsView.options.map(option => ({
id: option.id,
text: option.text,
...(option.supplementaryInfo && { supplementaryInfo: option.supplementaryInfo })
}))
};
};
/**
* Convert OptionsReadError to user-friendly message
*/
export const formatOptionsReadError = (error) => {
switch (error.type) {
case 'FileSystemError':
return `ファイルシステムエラー: ${error.message}`;
case 'DataCorruption':
return `データ破損エラー: ${error.message}${error.details ? ` (詳細: ${error.details})` : ''}`;
default:
return '不明なエラーが発生しました';
}
};
//# sourceMappingURL=index.js.map