Skip to content

Instantly share code, notes, and snippets.

@waspeer
Created April 29, 2024 16:50
Show Gist options
  • Save waspeer/1b791514648cf18183cec82d51d271a9 to your computer and use it in GitHub Desktop.
Save waspeer/1b791514648cf18183cec82d51d271a9 to your computer and use it in GitHub Desktop.
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