sfcc-dev-mcp
Version:
MCP server for Salesforce B2C Commerce Cloud development assistance including logs, debugging, and development tools
273 lines • 10.9 kB
JavaScript
/**
* SFCC Documentation Client (Refactored)
*
* This module provides functionality to query and retrieve SFCC class documentation
* from converted Markdown files. It orchestrates specialized modules to handle
* different aspects of documentation processing.
*
* Responsibilities:
* - Orchestrating specialized modules
* - Managing initialization and caching
* - Providing public API for documentation access
* - Applying filters and search functionality
*/
import { PathResolver } from '../utils/path-resolver.js';
import { CacheManager } from '../utils/cache.js';
import { Logger } from '../utils/logger.js';
import { DocumentationScanner } from './docs/documentation-scanner.js';
import { ClassContentParser } from './docs/class-content-parser.js';
import { ClassNameResolver } from './docs/class-name-resolver.js';
import { ReferencedTypesExtractor } from './docs/referenced-types-extractor.js';
export class SFCCDocumentationClient {
docsPath;
classCache = new Map();
cacheManager;
initialized = false;
logger;
documentationScanner;
classContentParser;
constructor() {
this.docsPath = PathResolver.getDocsPath();
this.cacheManager = new CacheManager();
this.logger = Logger.getChildLogger('DocsClient');
this.documentationScanner = new DocumentationScanner();
this.classContentParser = new ClassContentParser();
}
/**
* Initialize the documentation client by scanning all available classes
*/
async initialize() {
if (this.initialized) {
return;
}
try {
this.classCache = await this.documentationScanner.scanDocumentation(this.docsPath);
this.initialized = true;
}
catch (error) {
throw new Error(`Failed to initialize SFCC documentation: ${error}`);
}
}
/**
* Get a list of all available SFCC classes
*/
async getAvailableClasses() {
await this.initialize();
return Array.from(this.classCache.keys())
.sort()
.map(className => ClassNameResolver.toOfficialFormat(className));
}
/**
* Search for classes by name (partial matching)
*/
async searchClasses(query) {
await this.initialize();
// Check cache first
const cacheKey = `search:classes:${query.toLowerCase()}`;
const cachedResult = this.cacheManager.getSearchResults(cacheKey);
if (cachedResult) {
return cachedResult;
}
const lowercaseQuery = query.toLowerCase();
const results = Array.from(this.classCache.keys())
.filter(className => className.toLowerCase().includes(lowercaseQuery))
.sort()
.map(className => ClassNameResolver.toOfficialFormat(className));
// Cache the results
this.cacheManager.setSearchResults(cacheKey, results);
return results;
}
/**
* Get the raw documentation content for a class
*/
async getClassDocumentation(className) {
await this.initialize();
// Check cache first
const normalizedClassName = ClassNameResolver.normalizeClassName(className);
const cacheKey = `content:${normalizedClassName}`;
const cachedContent = this.cacheManager.getFileContent(cacheKey);
if (cachedContent !== undefined) {
return cachedContent || null;
}
// Resolve class name with fallback logic
const resolved = ClassNameResolver.resolveClassName(normalizedClassName, this.classCache);
const content = resolved ? resolved.info.content : null;
// Cache the result (including null results to avoid repeated lookups)
this.cacheManager.setFileContent(cacheKey, content ?? '');
return content;
}
/**
* Parse class documentation and extract structured information
*/
async getClassDetails(className) {
// Check cache first
const cacheKey = `details:${className}`;
const cachedDetails = this.cacheManager.getClassDetails(cacheKey);
if (cachedDetails !== undefined) {
return cachedDetails;
}
const content = await this.getClassDocumentation(className);
if (!content) {
// Cache null results to avoid repeated parsing attempts
this.cacheManager.setClassDetails(cacheKey, null);
return null;
}
const details = this.classContentParser.parseClassContent(content);
// Cache the parsed details
this.cacheManager.setClassDetails(cacheKey, details);
return details;
}
/**
* Get class details with optional expansion of referenced types and filtering
*/
async getClassDetailsExpanded(className, expand = false, filterOptions) {
// Set default filter options
const filters = {
includeDescription: filterOptions?.includeDescription ?? true,
includeConstants: filterOptions?.includeConstants ?? true,
includeProperties: filterOptions?.includeProperties ?? true,
includeMethods: filterOptions?.includeMethods ?? true,
includeInheritance: filterOptions?.includeInheritance ?? true,
search: filterOptions?.search,
};
// Check cache first for expanded details with filter options
const cacheKey = `details-expanded:${className}:${expand}:${JSON.stringify(filters)}`;
const cachedResult = this.cacheManager.getClassDetails(cacheKey);
if (cachedResult !== undefined) {
return cachedResult;
}
const classDetails = await this.getClassDetails(className);
if (!classDetails) {
this.cacheManager.setClassDetails(cacheKey, null);
return null;
}
// Apply filtering and search to the class details
const filteredDetails = this.applyFiltersAndSearch(classDetails, filters);
if (!expand) {
this.cacheManager.setClassDetails(cacheKey, filteredDetails);
return filteredDetails;
}
// Get the raw content to extract referenced types
const content = await this.getClassDocumentation(className);
if (!content) {
this.cacheManager.setClassDetails(cacheKey, filteredDetails);
return filteredDetails;
}
const referencedTypeNames = ReferencedTypesExtractor.extractFilteredReferencedTypes(content, className);
const referencedTypes = [];
// Get details for each referenced type
for (const typeName of referencedTypeNames) {
try {
const typeDetails = await this.getClassDetails(typeName);
if (typeDetails) {
referencedTypes.push(typeDetails);
}
}
catch {
// Silently skip types that can't be found
this.logger.warn(`Could not find details for referenced type: ${typeName}`);
}
}
const result = {
...filteredDetails,
referencedTypes: referencedTypes.length > 0 ? referencedTypes : undefined,
};
// Cache the result
this.cacheManager.setClassDetails(cacheKey, result);
return result;
}
/**
* Apply filters and search to class details
*/
applyFiltersAndSearch(classDetails, filters) {
const result = {
className: classDetails.className,
packageName: classDetails.packageName,
description: filters.includeDescription ? classDetails.description : '',
constants: [],
properties: [],
methods: [],
inheritance: filters.includeInheritance ? classDetails.inheritance : undefined,
constructorInfo: classDetails.constructorInfo,
};
// Apply search filter to constants
if (filters.includeConstants) {
result.constants = filters.search
? classDetails.constants.filter(constant => this.matchesSearch(constant.name, constant.description, filters.search))
: classDetails.constants;
}
// Apply search filter to properties
if (filters.includeProperties) {
result.properties = filters.search
? classDetails.properties.filter(property => this.matchesSearch(property.name, property.description, filters.search))
: classDetails.properties;
}
// Apply search filter to methods
if (filters.includeMethods) {
result.methods = filters.search
? classDetails.methods.filter(method => this.matchesSearch(method.name, method.description, filters.search) ||
this.matchesSearch(method.signature, '', filters.search))
: classDetails.methods;
}
// Apply search filter to inheritance
if (filters.includeInheritance && filters.search && result.inheritance) {
result.inheritance = result.inheritance.filter(inheritanceItem => this.matchesSearch(inheritanceItem, '', filters.search));
}
return result;
}
/**
* Check if a name or description matches the search term (case-insensitive)
*/
matchesSearch(name, description, searchTerm) {
const search = searchTerm.toLowerCase();
return (name.toLowerCase().includes(search) ||
description.toLowerCase().includes(search));
}
/**
* Search for methods across all classes
*/
async searchMethods(methodName) {
await this.initialize();
// Check cache first
const cacheKey = `search:methods:${methodName.toLowerCase()}`;
const cachedResult = this.cacheManager.getMethodSearch(cacheKey);
if (cachedResult) {
return cachedResult;
}
const results = [];
for (const [fullClassName] of this.classCache) {
const details = await this.getClassDetails(fullClassName);
const methods = details?.methods ?? [];
for (const method of methods) {
if (method.name.toLowerCase().includes(methodName.toLowerCase())) {
results.push({
className: fullClassName,
method,
});
}
}
}
// Cache the search results
this.cacheManager.setMethodSearch(cacheKey, results);
return results;
}
/**
* Get cache statistics for monitoring performance
*/
getCacheStats() {
return this.cacheManager.getAllStats();
}
/**
* Clear all caches
*/
clearCache() {
this.cacheManager.clearAll();
}
/**
* Cleanup resources and destroy caches
*/
destroy() {
this.cacheManager.destroy();
}
}
//# sourceMappingURL=docs-client.js.map