import createImageUrlBuilder from '@sanity/image-url'; // import { sanityClient } from 'sanity:client'; import createClient from 'picosanity'; import { z } from 'zod'; const sanityClient = createClient({ dataset: 'production', projectId: 'maa8c2eu', apiVersion: '2024-04-29', useCdn: import.meta.env.PROD, }); const RECHECK_INTERVAL = 5 * 60 * 1000; const RichText = z.array(z.any()); const Show = z.object({ title: z.string(), venue: z.string(), city: z.string(), date: z.string(), ticketLink: z.string().optional(), }); export type Show = z.infer; const SocialLink = z.object({ icon: z.object({ name: z.string(), }), label: z.string(), url: z.string(), }); export type SocialLink = z.infer; export const createImageURL = createImageUrlBuilder(sanityClient); export const fetchAbout = createFetcher({ key: 'about', query: /* groq */ ` *[_id == "about"][0] { title, descriptionShort, descriptionLong, } `, schema: z.object({ title: z.string(), descriptionShort: RichText, descriptionLong: RichText, }), }); export const fetchContact = createFetcher({ key: 'contact', query: /* groq */ ` *[_id == "contact"][0] { body, footer, } `, schema: z.object({ body: RichText, footer: RichText, }), }); export const fetchUpcomingShows = createFetcher({ key: 'shows', query: /* groq */ ` *[_type == "show" && date >= now()] | order(date desc) { title, venue, city, date, ticketLink, } `, schema: z.array(Show), }); export const fetchMetadata = createFetcher({ key: 'metadata', query: /* groq */ ` *[_id == "metadata"][0] { title, description, image, } `, schema: z.object({ title: z.string(), description: z.string(), image: z.object({ asset: z.object({ _ref: z.string(), }), }), }), }); export const fetchSocials = createFetcher({ key: 'socialLinks', query: /* groq */ ` *[_id == "socials"][0] { socialLinks[] { icon, label, url, } } `, schema: z.object({ socialLinks: z.array(SocialLink), }), }); export const fetchFriends = createFetcher({ key: 'friends', query: /* groq */ ` *[_id == "friends"][0] { title, body, embed, } `, schema: z.object({ title: z.string(), body: RichText, embed: z.object({ code: z.string(), }), }), }); /** * Creates a data fetcher function for the given options. * * @param options - The fetcher options containing: * - key: Unique cache key * - query: The query to execute * - schema: The schema to validate the query result against * @returns A function that when called will fetch the data. */ function createFetcher(options: { key: string; query: string; schema: z.ZodType }) { const { key, query, schema } = options; return async function fetcher() { const data = await fetchQuery(key, query); const parseResult = schema.safeParse(data); if (!parseResult.success) { console.error(parseResult.error); throw new Error(`Failed to parse data for "${key}"`, { cause: parseResult.error }); } return parseResult.data; }; } /** * Returns a cached map of Sanity data. * The cache is invalidated and rebuilt when the Sanity dataset changes. */ const getCache = (() => { let lastCheck: number | null = null; let lastModified: string | null = null; let cache = new Map(); async function getLastModified(): Promise { if (lastCheck) { if (Date.now() - lastCheck < RECHECK_INTERVAL) { return Promise.resolve(lastModified); } } lastCheck = Date.now(); console.time('lastModified'); const newLastModified = await sanityClient.fetch( lastModified ? /* groq */ `*[!(_type match 'system.*') && _updatedAt >= $lastModified] | order(_updatedAt desc)[0]._updatedAt` : /* groq */ `*[!(_type match 'system.*')] | order(_updatedAt desc)[0]._updatedAt`, { lastModified }, { perspective: 'published' }, ); console.timeEnd('lastModified'); return newLastModified; } return sharePromise(async function getCache() { const newLastModified = await getLastModified(); if (newLastModified !== lastModified) { lastModified = newLastModified; cache = new Map(); } return cache; }); })(); /** * Queries the Sanity dataset with the given query. * Caches the response to avoid unnecessary API calls if the same query is made again. * * @param key - The cache key * @param query - The Groq query to execute */ async function fetchQuery(key: string, query: string) { const cache = await getCache(); const cacheHit = cache.has(key); if (!cacheHit) { console.time(key); cache.set(key, sanityClient.fetch(query)); } const data = await cache.get(key)!; if (!cacheHit) { console.timeEnd(key); } return data; } function sharePromise(fn: () => Promise): () => Promise { let cachedPromise: Promise | null = null; return async () => { cachedPromise ??= fn(); const data = await cachedPromise; cachedPromise = null; return data; }; }