Add cached-first TeamSnap reads
This commit is contained in:
86
frontend/src/lib/teamSnapCache.ts
Normal file
86
frontend/src/lib/teamSnapCache.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { QueryKey } from "@tanstack/react-query";
|
||||
|
||||
import { queryClient } from "./queryClient";
|
||||
import { readCachedValue, writeCachedValue } from "./offlineCache";
|
||||
|
||||
const inFlightRequests = new Map<string, Promise<unknown>>();
|
||||
|
||||
export function normalizeTeamSnapCacheScope(scope?: string | number | null): string {
|
||||
const value = scope == null ? "" : String(scope).trim();
|
||||
return value || "anonymous";
|
||||
}
|
||||
|
||||
function buildCacheParts(scope: string | number | null | undefined, resource: string, parts: readonly unknown[]): readonly unknown[] {
|
||||
return ["teamsnap", normalizeTeamSnapCacheScope(scope), resource, ...parts];
|
||||
}
|
||||
|
||||
function cacheKey(parts: readonly unknown[]): string {
|
||||
return JSON.stringify(parts);
|
||||
}
|
||||
|
||||
function startFreshFetch<T>(
|
||||
cacheParts: readonly unknown[],
|
||||
queryKey: QueryKey,
|
||||
fetchFresh: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
const key = cacheKey(cacheParts);
|
||||
const existing = inFlightRequests.get(key);
|
||||
if (existing) {
|
||||
return existing as Promise<T>;
|
||||
}
|
||||
|
||||
const request = fetchFresh()
|
||||
.then((data) => {
|
||||
writeCachedValue(cacheParts, data);
|
||||
queryClient.setQueryData(queryKey, data);
|
||||
return data;
|
||||
})
|
||||
.finally(() => {
|
||||
inFlightRequests.delete(key);
|
||||
});
|
||||
|
||||
inFlightRequests.set(key, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
export async function staleWhileRevalidateTeamSnap<T>({
|
||||
cacheParts,
|
||||
queryKey,
|
||||
fetchFresh,
|
||||
}: {
|
||||
cacheParts: readonly unknown[];
|
||||
queryKey: QueryKey;
|
||||
fetchFresh: () => Promise<T>;
|
||||
}): Promise<T> {
|
||||
const cached = readCachedValue<T>(cacheParts);
|
||||
if (cached) {
|
||||
void startFreshFetch(cacheParts, queryKey, fetchFresh).catch(() => undefined);
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
return startFreshFetch(cacheParts, queryKey, fetchFresh);
|
||||
}
|
||||
|
||||
export const teamSnapQueryKeys = {
|
||||
me(scope?: string | number | null): QueryKey {
|
||||
return buildCacheParts(scope, "me", []);
|
||||
},
|
||||
teams(scope?: string | number | null): QueryKey {
|
||||
return buildCacheParts(scope, "teams", []);
|
||||
},
|
||||
members(scope: string | number | null | undefined, teamId: string): QueryKey {
|
||||
return buildCacheParts(scope, "members", [teamId]);
|
||||
},
|
||||
events(scope: string | number | null | undefined, teamId: string): QueryKey {
|
||||
return buildCacheParts(scope, "events", [teamId]);
|
||||
},
|
||||
availabilities(scope: string | number | null | undefined, teamId: string, eventId?: string): QueryKey {
|
||||
return buildCacheParts(scope, "availabilities", [teamId, eventId ?? ""]);
|
||||
},
|
||||
assignments(scope: string | number | null | undefined, teamId: string, eventId?: string): QueryKey {
|
||||
return buildCacheParts(scope, "assignments", [teamId, eventId ?? ""]);
|
||||
},
|
||||
eventLineup(scope: string | number | null | undefined, teamId: string, eventId: string): QueryKey {
|
||||
return buildCacheParts(scope, "eventLineup", [teamId, eventId]);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user