UNPKG

@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
"use strict"; /** * @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, }; }