@ai-growth/nextjs
Version:
Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering
427 lines (426 loc) • 15.2 kB
JavaScript
;
/**
* @fileoverview Enhanced CMS Content Hooks with Advanced Caching
*
* This module provides React hooks for fetching CMS content with comprehensive
* caching strategies, SWR patterns, and cache invalidation capabilities.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.useCmsContentCached = useCmsContentCached;
exports.useCmsContentByIdCached = useCmsContentByIdCached;
exports.useCacheInvalidation = useCacheInvalidation;
exports.useCachePreloading = useCachePreloading;
exports.useCacheMonitoring = useCacheMonitoring;
const react_1 = require("react");
const utils_1 = require("../utils");
const cache_manager_1 = require("../utils/cache-manager");
// ============================================================================
// ENHANCED CMS CONTENT HOOKS
// ============================================================================
/**
* Enhanced hook for fetching CMS content by slug with advanced caching
*/
function useCmsContentCached(slug, options = {}) {
const { contentType = 'post', initialData = null, enabled = true, ttl = 30 * 60 * 1000, // 30 minutes
staleTime = 5 * 60 * 1000, // 5 minutes
revalidateInBackground = true, refetchOnWindowFocus = false, refetchOnReconnect = true, onSuccess, onError, cacheTags = [], ...fetchOptions } = options;
// State management
const [content, setContent] = (0, react_1.useState)(initialData);
const [isLoading, setIsLoading] = (0, react_1.useState)(!initialData && enabled && !!slug);
const [isError, setIsError] = (0, react_1.useState)(false);
const [error, setError] = (0, react_1.useState)(null);
const [isFetching, setIsFetching] = (0, react_1.useState)(false);
const [isStale, setIsStale] = (0, react_1.useState)(false);
const [isRevalidating, setIsRevalidating] = (0, react_1.useState)(false);
const [cacheHit, setCacheHit] = (0, react_1.useState)(false);
// Generate cache key
const cacheKey = (0, react_1.useMemo)(() => {
if (!slug)
return null;
return cache_manager_1.CacheKeys.content(contentType, slug);
}, [slug, contentType]);
// Enhanced fetcher function
const fetcher = (0, react_1.useCallback)(async () => {
const data = await (0, utils_1.fetchContentBySlug)(contentType, slug, fetchOptions);
if (!data) {
throw new Error(`Content not found for slug: ${slug}`);
}
return data;
}, [contentType, slug, fetchOptions]);
// Fetch content with SWR pattern
const fetchContent = (0, react_1.useCallback)(async (isBackground = false) => {
if (!slug || !enabled || !cacheKey)
return;
try {
setIsFetching(true);
if (isBackground) {
setIsRevalidating(true);
}
else {
setIsError(false);
setError(null);
}
const result = await cache_manager_1.defaultCacheManager.getWithSWR(cacheKey, fetcher, {
ttl,
staleTime,
tags: [cache_manager_1.CacheTags.CONTENT, contentType, ...cacheTags],
revalidateInBackground,
});
setContent(result.data);
setIsStale(result.stale);
setCacheHit(result.hit);
setIsLoading(false);
if (result.data) {
onSuccess?.(result.data);
}
if (result.revalidating) {
setIsRevalidating(true);
}
else {
setIsRevalidating(false);
}
}
catch (err) {
const errorObj = err instanceof Error ? err : new Error(String(err));
setError(errorObj);
setIsError(true);
setContent(null);
setIsLoading(false);
setIsRevalidating(false);
onError?.(errorObj);
}
finally {
setIsFetching(false);
}
}, [
slug,
enabled,
cacheKey,
fetcher,
ttl,
staleTime,
contentType,
cacheTags,
revalidateInBackground,
onSuccess,
onError,
]);
// Manual refetch function
const refetch = (0, react_1.useCallback)(async () => {
if (cacheKey) {
await cache_manager_1.defaultCacheManager.delete(cacheKey);
}
await fetchContent(false);
}, [cacheKey, fetchContent]);
// Reset function
const reset = (0, react_1.useCallback)(() => {
setContent(initialData);
setIsLoading(!initialData && enabled && !!slug);
setIsError(false);
setError(null);
setIsFetching(false);
setIsStale(false);
setIsRevalidating(false);
setCacheHit(false);
}, [initialData, enabled, slug]);
// Invalidate cache
const invalidate = (0, react_1.useCallback)(async () => {
if (cacheKey) {
await cache_manager_1.defaultCacheManager.delete(cacheKey);
}
}, [cacheKey]);
// Initial fetch effect
(0, react_1.useEffect)(() => {
if (slug && enabled) {
fetchContent(false);
}
}, [slug, enabled, fetchContent]);
// Window focus refetch
(0, react_1.useEffect)(() => {
if (!refetchOnWindowFocus)
return;
const handleFocus = () => {
if (slug && enabled && isStale) {
fetchContent(true);
}
};
window.addEventListener('focus', handleFocus);
return () => window.removeEventListener('focus', handleFocus);
}, [refetchOnWindowFocus, slug, enabled, isStale, fetchContent]);
// Online/reconnect refetch
(0, react_1.useEffect)(() => {
if (!refetchOnReconnect)
return;
const handleOnline = () => {
if (slug && enabled && isStale) {
fetchContent(true);
}
};
window.addEventListener('online', handleOnline);
return () => window.removeEventListener('online', handleOnline);
}, [refetchOnReconnect, slug, enabled, isStale, fetchContent]);
return {
content,
isLoading,
isError,
error,
isFetching,
isStale,
isRevalidating,
cacheHit,
refetch,
reset,
invalidate,
};
}
/**
* Enhanced hook for fetching CMS content by ID with caching
*/
function useCmsContentByIdCached(id, options = {}) {
const { initialData = null, enabled = true, ttl = 30 * 60 * 1000, staleTime = 5 * 60 * 1000, revalidateInBackground = true, onSuccess, onError, cacheTags = [], ...fetchOptions } = options;
// State management
const [content, setContent] = (0, react_1.useState)(initialData);
const [isLoading, setIsLoading] = (0, react_1.useState)(!initialData && enabled && !!id);
const [isError, setIsError] = (0, react_1.useState)(false);
const [error, setError] = (0, react_1.useState)(null);
const [isFetching, setIsFetching] = (0, react_1.useState)(false);
const [isStale, setIsStale] = (0, react_1.useState)(false);
const [isRevalidating, setIsRevalidating] = (0, react_1.useState)(false);
const [cacheHit, setCacheHit] = (0, react_1.useState)(false);
// Generate cache key
const cacheKey = (0, react_1.useMemo)(() => {
if (!id)
return null;
return cache_manager_1.CacheKeys.contentById(id);
}, [id]);
// Enhanced fetcher function
const fetcher = (0, react_1.useCallback)(async () => {
const data = await (0, utils_1.fetchContentById)(id, fetchOptions);
if (!data) {
throw new Error(`Content not found for ID: ${id}`);
}
return data;
}, [id, fetchOptions]);
// Fetch content with SWR pattern
const fetchContent = (0, react_1.useCallback)(async (isBackground = false) => {
if (!id || !enabled || !cacheKey)
return;
try {
setIsFetching(true);
if (isBackground) {
setIsRevalidating(true);
}
else {
setIsError(false);
setError(null);
}
const result = await cache_manager_1.defaultCacheManager.getWithSWR(cacheKey, fetcher, {
ttl,
staleTime,
tags: [cache_manager_1.CacheTags.CONTENT, ...cacheTags],
revalidateInBackground,
});
setContent(result.data);
setIsStale(result.stale);
setCacheHit(result.hit);
setIsLoading(false);
if (result.data) {
onSuccess?.(result.data);
}
if (result.revalidating) {
setIsRevalidating(true);
}
else {
setIsRevalidating(false);
}
}
catch (err) {
const errorObj = err instanceof Error ? err : new Error(String(err));
setError(errorObj);
setIsError(true);
setContent(null);
setIsLoading(false);
setIsRevalidating(false);
onError?.(errorObj);
}
finally {
setIsFetching(false);
}
}, [
id,
enabled,
cacheKey,
fetcher,
ttl,
staleTime,
cacheTags,
revalidateInBackground,
onSuccess,
onError,
]);
// Manual refetch function
const refetch = (0, react_1.useCallback)(async () => {
if (cacheKey) {
await cache_manager_1.defaultCacheManager.delete(cacheKey);
}
await fetchContent(false);
}, [cacheKey, fetchContent]);
// Reset function
const reset = (0, react_1.useCallback)(() => {
setContent(initialData);
setIsLoading(!initialData && enabled && !!id);
setIsError(false);
setError(null);
setIsFetching(false);
setIsStale(false);
setIsRevalidating(false);
setCacheHit(false);
}, [initialData, enabled, id]);
// Invalidate cache
const invalidate = (0, react_1.useCallback)(async () => {
if (cacheKey) {
await cache_manager_1.defaultCacheManager.delete(cacheKey);
}
}, [cacheKey]);
// Initial fetch effect
(0, react_1.useEffect)(() => {
if (id && enabled) {
fetchContent(false);
}
}, [id, enabled, fetchContent]);
return {
content,
isLoading,
isError,
error,
isFetching,
isStale,
isRevalidating,
cacheHit,
refetch,
reset,
invalidate,
};
}
// ============================================================================
// CACHE INVALIDATION HOOKS
// ============================================================================
/**
* Hook for cache invalidation operations
*/
function useCacheInvalidation() {
const invalidateContent = (0, react_1.useCallback)(async (contentType, slug) => {
if (slug) {
const key = cache_manager_1.CacheKeys.content(contentType, slug);
await cache_manager_1.defaultCacheManager.delete(key);
}
else {
await cache_manager_1.defaultCacheManager.invalidate({
strategy: 'tag-based',
tags: [contentType],
});
}
}, []);
const invalidateContentLists = (0, react_1.useCallback)(async (contentType) => {
if (contentType) {
await cache_manager_1.defaultCacheManager.invalidate({
strategy: 'tag-based',
tags: [cache_manager_1.CacheTags.CONTENT_LIST, contentType],
});
}
else {
await cache_manager_1.defaultCacheManager.invalidate({
strategy: 'tag-based',
tags: [cache_manager_1.CacheTags.CONTENT_LIST],
});
}
}, []);
const invalidateByTags = (0, react_1.useCallback)(async (tags) => {
await cache_manager_1.defaultCacheManager.invalidate({
strategy: 'tag-based',
tags,
});
}, []);
const invalidateByPattern = (0, react_1.useCallback)(async (pattern) => {
await cache_manager_1.defaultCacheManager.invalidate({
strategy: 'manual',
pattern,
});
}, []);
const clearAllCache = (0, react_1.useCallback)(async () => {
await cache_manager_1.defaultCacheManager.clear();
}, []);
return {
invalidateContent,
invalidateContentLists,
invalidateByTags,
invalidateByPattern,
clearAllCache,
};
}
/**
* Hook for cache preloading
*/
function useCachePreloading() {
const preloadContent = (0, react_1.useCallback)(async (contentType, slug, fetchOptions) => {
const key = cache_manager_1.CacheKeys.content(contentType, slug);
const status = await cache_manager_1.defaultCacheManager.has(key);
if (!status.exists || status.stale) {
try {
const data = await (0, utils_1.fetchContentBySlug)(contentType, slug, fetchOptions);
if (data) {
await cache_manager_1.defaultCacheManager.set(key, data, {
tags: [cache_manager_1.CacheTags.CONTENT, contentType],
});
}
}
catch (error) {
console.warn(`Failed to preload content ${contentType}:${slug}`, error);
}
}
}, []);
const preloadContentById = (0, react_1.useCallback)(async (id, fetchOptions) => {
const key = cache_manager_1.CacheKeys.contentById(id);
const status = await cache_manager_1.defaultCacheManager.has(key);
if (!status.exists || status.stale) {
try {
const data = await (0, utils_1.fetchContentById)(id, fetchOptions);
if (data) {
await cache_manager_1.defaultCacheManager.set(key, data, {
tags: [cache_manager_1.CacheTags.CONTENT],
});
}
}
catch (error) {
console.warn(`Failed to preload content ID ${id}`, error);
}
}
}, []);
return {
preloadContent,
preloadContentById,
};
}
/**
* Hook for cache monitoring and debugging
*/
function useCacheMonitoring() {
const [metrics, setMetrics] = (0, react_1.useState)(() => cache_manager_1.defaultCacheManager.getMetrics());
const refreshMetrics = (0, react_1.useCallback)(() => {
setMetrics(cache_manager_1.defaultCacheManager.getMetrics());
}, []);
const resetMetrics = (0, react_1.useCallback)(() => {
cache_manager_1.defaultCacheManager.resetMetrics();
refreshMetrics();
}, [refreshMetrics]);
// Auto-refresh metrics
(0, react_1.useEffect)(() => {
const interval = setInterval(refreshMetrics, 10000); // Every 10 seconds
return () => clearInterval(interval);
}, [refreshMetrics]);
return {
metrics,
refreshMetrics,
resetMetrics,
};
}