UNPKG

@ai-growth/nextjs

Version:

Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering

305 lines (304 loc) 10.2 kB
import { createClient } from '@sanity/client'; import { getSanityClientConfig, isClient } from './config'; /** * Cache for the Sanity client instance to avoid recreating it multiple times */ let sanityClientCache = null; /** * Creates a Sanity client instance using the configuration from environment variables. * * This function automatically configures the client based on the current environment: * - Uses CDN in production when no token is available (read-only access) * - Disables CDN when authenticated or in development (real-time data) * - Handles both server-side and client-side environments * * @param options - Optional configuration overrides * @param options.useCache - Whether to cache the client instance (default: true) * @param options.configOverrides - Manual configuration overrides for testing * @returns Configured Sanity client instance * * @example * ```typescript * // Basic usage - uses environment configuration * const client = getSanityClient(); * * // Disable caching for testing * const client = getSanityClient({ useCache: false }); * * // Manual configuration override * const client = getSanityClient({ * configOverrides: { projectId: 'test-project' } * }); * ``` * * @throws {ConfigurationError} When environment configuration is invalid */ export function getSanityClient(options = {}) { const { useCache = true, configOverrides } = options; // Return cached client if available and caching is enabled if (useCache && sanityClientCache) { return sanityClientCache; } // Get configuration from environment (or use overrides for testing) const config = configOverrides ? { ...getSanityClientConfig(), ...configOverrides } : getSanityClientConfig(); // Create client configuration const clientConfig = { projectId: config.projectId, dataset: config.dataset, apiVersion: config.apiVersion, useCdn: config.useCdn ?? true, }; // Only include token if it's available if (config.token) { clientConfig.token = config.token; } // Create the client const client = createClient(clientConfig); // Cache the client if caching is enabled if (useCache) { sanityClientCache = client; } return client; } /** * Clears the cached Sanity client instance. * * This is useful for testing scenarios where you need to ensure * a fresh client is created with different configuration. * * @example * ```typescript * // Clear cache before testing with different config * clearSanityClientCache(); * const client = getSanityClient(); * ``` */ export function clearSanityClientCache() { sanityClientCache = null; } /** * Gets the current cached Sanity client instance without creating a new one. * * @returns The cached client instance or null if no client is cached * * @example * ```typescript * const cachedClient = getCachedSanityClient(); * if (cachedClient) { * // Use existing client * } else { * // Create new client * const client = getSanityClient(); * } * ``` */ export function getCachedSanityClient() { return sanityClientCache; } /** * Creates a new Sanity client instance without caching. * * This is useful when you need a fresh client instance, * for example in testing or when working with multiple projects. * * @param configOverrides - Manual configuration overrides * @returns New Sanity client instance * * @example * ```typescript * // Create fresh client for testing * const testClient = createSanityClient({ * projectId: 'test-project', * dataset: 'test' * }); * ``` */ export function createSanityClient(configOverrides) { return getSanityClient({ useCache: false, ...(configOverrides && { configOverrides }), }); } /** * Verifies the connection to Sanity API by performing a lightweight query. * * This function tests the API connection by attempting a minimal query * to the Sanity Content Lake. It measures latency, handles timeouts, * and categorizes different types of connection failures. * * @param options - Configuration options for connection verification * @param options.timeout - Request timeout in milliseconds (default: 10000) * @param options.retryAttempts - Number of retry attempts for transient failures (default: 1) * @param options.client - Custom client instance to use for testing * @returns Promise resolving to connection status information * * @example * ```typescript * // Basic connection verification * const status = await verifyConnection(); * if (status.connected) { * console.log(`Connected! Latency: ${status.latency}ms`); * } else { * console.error(`Connection failed: ${status.error}`); * } * * // Custom timeout and retry * const status = await verifyConnection({ * timeout: 5000, * retryAttempts: 2 * }); * ``` */ export async function verifyConnection(options = {}) { const { timeout = 10000, retryAttempts = 1, client: providedClient } = options; const startTime = Date.now(); const timestamp = new Date().toISOString(); const environment = isClient() ? 'client' : 'server'; let attempt = 0; let lastError = null; while (attempt <= retryAttempts) { try { // Get or use provided client const client = providedClient || getSanityClient({ useCache: false }); // Create a promise that will timeout const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Connection timeout')), timeout); }); // Try a lightweight system query first const queryPromise = client.fetch(`*[_type == "sanity.imageAsset"][0]._id`); // Race between query and timeout await Promise.race([queryPromise, timeoutPromise]); // If we get here, connection succeeded const latency = Date.now() - startTime; return { connected: true, latency, timestamp, environment, details: { retryAttempts: attempt, queryType: 'system', timedOut: false, }, }; } catch (error) { lastError = error; attempt++; // If this was the last attempt, break if (attempt > retryAttempts) { break; } // Wait before retry (exponential backoff) if (attempt <= retryAttempts) { await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); } } } // Connection failed after all attempts const latency = Date.now() - startTime; const errorMessage = lastError?.message || 'Unknown connection error'; // Categorize error types let categorizedError = errorMessage; if (errorMessage.includes('timeout') || errorMessage.includes('TIMEOUT')) { categorizedError = 'Connection timeout - please check your network connection'; } else if (errorMessage.includes('Unauthorized') || errorMessage.includes('401')) { categorizedError = 'Authentication failed - please check your API token'; } else if (errorMessage.includes('Not Found') || errorMessage.includes('404')) { categorizedError = 'Project not found - please check your project ID and dataset'; } else if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('DNS')) { categorizedError = 'Network error - unable to reach Sanity API'; } return { connected: false, error: categorizedError, latency, timestamp, environment, details: { retryAttempts: attempt - 1, queryType: 'system', timedOut: errorMessage.includes('timeout'), }, }; } /** * Performs a quick connection health check with default settings. * * This is a convenience function that performs connection verification * with sensible defaults for most use cases. * * @returns Promise resolving to boolean indicating connection status * * @example * ```typescript * if (await isConnected()) { * console.log('Sanity API is accessible'); * } else { * console.log('Cannot reach Sanity API'); * } * ``` */ export async function isConnected() { try { const status = await verifyConnection({ timeout: 5000, retryAttempts: 0 }); return status.connected; } catch { return false; } } /** * Gets detailed connection information including configuration status. * * This function provides comprehensive connection diagnostics including * configuration validation, API accessibility, and environment details. * * @param options - Configuration options * @param options.includeConfig - Whether to include configuration details in response * @returns Promise resolving to detailed connection information * * @example * ```typescript * const info = await getConnectionInfo({ includeConfig: true }); * console.log('Environment:', info.environment); * console.log('Connected:', info.connected); * if (info.apiVersion) { * console.log('API Version:', info.apiVersion); * } * ``` */ export async function getConnectionInfo(options = {}) { const { includeConfig = false } = options; let configValid = true; if (includeConfig) { try { // Test configuration validity getSanityClientConfig(); } catch { configValid = false; } } // Get connection status const connectionStatus = await verifyConnection(); // Try to get API version from config if connection succeeded if (connectionStatus.connected && includeConfig) { try { const config = getSanityClientConfig(); connectionStatus.apiVersion = config.apiVersion; } catch { // Config error already handled above } } return { ...connectionStatus, ...(includeConfig && { configValid }), }; }