Created
April 29, 2024 16:50
-
-
Save waspeer/1b791514648cf18183cec82d51d271a9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<typeof Show>; | |
const SocialLink = z.object({ | |
icon: z.object({ | |
name: z.string(), | |
}), | |
label: z.string(), | |
url: z.string(), | |
}); | |
export type SocialLink = z.infer<typeof SocialLink>; | |
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<TData>(options: { key: string; query: string; schema: z.ZodType<TData> }) { | |
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<string, any>(); | |
async function getLastModified(): Promise<string | null> { | |
if (lastCheck) { | |
if (Date.now() - lastCheck < RECHECK_INTERVAL) { | |
return Promise.resolve(lastModified); | |
} | |
} | |
lastCheck = Date.now(); | |
console.time('lastModified'); | |
const newLastModified = await sanityClient.fetch<string | null>( | |
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<T>(fn: () => Promise<T>): () => Promise<T> { | |
let cachedPromise: Promise<T> | null = null; | |
return async () => { | |
cachedPromise ??= fn(); | |
const data = await cachedPromise; | |
cachedPromise = null; | |
return data; | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment