@ai-growth/nextjs
Version:
Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering
244 lines (243 loc) • 8.28 kB
JavaScript
/**
* Enhanced template resolver with advanced features
*/
export class TemplateResolver {
constructor(templateRegistry) {
this.partialOverrides = [];
this.resolutionCache = new Map();
this.templateRegistry = templateRegistry;
}
/**
* Register a partial template override
*/
registerPartialOverride(override) {
this.partialOverrides.push(override);
this.clearCache();
}
/**
* Remove all partial overrides
*/
clearPartialOverrides() {
this.partialOverrides = [];
this.clearCache();
}
/**
* Update the template registry
*/
updateRegistry(newRegistry) {
this.templateRegistry = newRegistry;
this.clearCache();
}
/**
* Clear resolution cache
*/
clearCache() {
this.resolutionCache.clear();
}
/**
* Resolve template for a content type with advanced logic
*/
resolveTemplate(contentType, options = {}) {
const cacheKey = this.getCacheKey(contentType, options);
// Check cache first
if (this.resolutionCache.has(cacheKey)) {
return this.resolutionCache.get(cacheKey);
}
const result = this.performResolution(contentType, options);
// Cache the result
this.resolutionCache.set(cacheKey, result);
if (options.debug) {
console.log('Template Resolution:', {
contentType,
result,
resolutionPath: result.resolutionPath,
});
}
return result;
}
/**
* Get partial overrides for a content type
*/
getPartialOverrides(contentType) {
return this.partialOverrides.filter(override => override.contentTypes.some(ct => this.matchesContentType(contentType, ct)));
}
/**
* Create a template component with partial overrides applied
*/
createTemplateWithOverrides(baseTemplate, contentType) {
const overrides = this.getPartialOverrides(contentType);
if (overrides.length === 0) {
return baseTemplate;
}
// For now, return the base template
// TODO: Implement proper template composition with React.createElement
// This would require more complex React component handling
return baseTemplate;
}
/**
* Perform the actual template resolution
*/
performResolution(contentType, options) {
const resolutionPath = [];
// 1. Try exact match
resolutionPath.push('checking exact match');
if (this.templateRegistry[contentType]) {
const template = this.createTemplateWithOverrides(this.templateRegistry[contentType], contentType);
return {
template,
matchType: 'exact',
matchedContentType: contentType,
resolutionPath,
};
}
// 2. Try wildcard patterns
resolutionPath.push('checking wildcard patterns');
const wildcardMatch = this.findWildcardMatch(contentType);
if (wildcardMatch) {
const template = this.createTemplateWithOverrides(this.templateRegistry[wildcardMatch], contentType);
return {
template,
matchType: 'wildcard',
matchedContentType: wildcardMatch,
resolutionPath,
};
}
// 3. Try parent type matching (e.g., "blog" for "blog.post")
resolutionPath.push('checking parent types');
const parentMatch = this.findParentMatch(contentType);
if (parentMatch) {
const template = this.createTemplateWithOverrides(this.templateRegistry[parentMatch], contentType);
return {
template,
matchType: 'parent',
matchedContentType: parentMatch,
resolutionPath,
};
}
// 4. Try content type hierarchy
if (options.contentTypeHierarchy) {
resolutionPath.push('checking content type hierarchy');
for (const hierarchyType of options.contentTypeHierarchy) {
if (this.templateRegistry[hierarchyType]) {
const template = this.createTemplateWithOverrides(this.templateRegistry[hierarchyType], contentType);
return {
template,
matchType: 'hierarchy',
matchedContentType: hierarchyType,
resolutionPath,
};
}
}
}
// 5. Try fallback template
if (options.fallbackTemplate) {
resolutionPath.push('using provided fallback');
const template = this.createTemplateWithOverrides(options.fallbackTemplate, contentType);
return {
template,
matchType: 'fallback',
resolutionPath,
};
}
// 6. Try default template from registry
if (this.templateRegistry.default) {
resolutionPath.push('using registry default');
const template = this.createTemplateWithOverrides(this.templateRegistry.default, contentType);
return {
template,
matchType: 'fallback',
matchedContentType: 'default',
resolutionPath,
};
}
// 7. No template found
resolutionPath.push('no template found');
return {
template: null,
matchType: 'none',
resolutionPath,
};
}
/**
* Find wildcard pattern match
*/
findWildcardMatch(contentType) {
const wildcardKeys = Object.keys(this.templateRegistry).filter(key => key.includes('*'));
for (const wildcardKey of wildcardKeys) {
if (this.matchesWildcardPattern(contentType, wildcardKey)) {
return wildcardKey;
}
}
return null;
}
/**
* Find parent type match
*/
findParentMatch(contentType) {
const parts = contentType.split('.');
// Try progressively shorter parent types
for (let i = parts.length - 1; i > 0; i--) {
const parentType = parts.slice(0, i).join('.');
if (this.templateRegistry[parentType]) {
return parentType;
}
}
return null;
}
/**
* Check if content type matches a wildcard pattern
*/
matchesWildcardPattern(contentType, pattern) {
const regexPattern = pattern.replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(contentType);
}
/**
* Check if content type matches another content type (including wildcards)
*/
matchesContentType(contentType, targetType) {
if (contentType === targetType) {
return true;
}
if (targetType.includes('*')) {
return this.matchesWildcardPattern(contentType, targetType);
}
return false;
}
/**
* Generate cache key for resolution result
*/
getCacheKey(contentType, options) {
return JSON.stringify({
contentType,
fallbackTemplate: options.fallbackTemplate?.name || 'none',
hierarchy: options.contentTypeHierarchy || [],
overrides: this.partialOverrides.length,
});
}
/**
* Get resolution statistics
*/
getStatistics() {
return {
cacheSize: this.resolutionCache.size,
registrySize: Object.keys(this.templateRegistry).length,
overridesCount: this.partialOverrides.length,
};
}
}
/**
* Create a template resolver instance
*/
export function createTemplateResolver(templateRegistry) {
return new TemplateResolver(templateRegistry);
}
/**
* Utility function to resolve template with simple interface
*/
export function resolveTemplate(contentType, templateRegistry, options) {
const resolver = new TemplateResolver(templateRegistry);
const result = resolver.resolveTemplate(contentType, options);
return result.template;
}
export default TemplateResolver;