Something went wrong. Try again.
🌿 Collaborative wiki on ATProto
Something went wrong. Try again.
2.5 kB · 65 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566import { expireCachedProfile, hasCachedProfile, isDidBanned, listWikiOwnerDids,} from "../server/db/queries/index.ts";import { resolveProfile } from "./profile.ts";
// Injectable so tests exercise the sweep, not the network. resolveProfile's own// `fetchFn` hook is not usable here: every setCachedProfile call inside it is// guarded on `fetchFn === fetch`, so injecting one resolves without ever writing.export type ProfileResolver = (did: string) => Promise<unknown>;
// Two at a time: this is maintenance, and each resolve is two upstream requests// (plc.directory, then the Bluesky appview). No reason to burst 45 of them.const SWEEP_CONCURRENCY = 2;
// Listing queries read profile_cache with no freshness predicate, and nothing on a// listing path resolves. So a renamed owner's handle goes stale and stays stale —// the profile page that would refresh it sits behind the dead link it produces.// This is the only thing that reliably breaks that loop; identity events are the// fast path, not the guarantee.//// No forced expiry here: resolveProfile short-circuits inside profileCacheHours,// so the TTL decides what actually costs a request. A sweep of 45 owners where// none are stale makes zero network calls.export async function sweepOwnerHandles( resolve: ProfileResolver = resolveProfile,): Promise<number> { const dids = listWikiOwnerDids().filter((did) => !isDidBanned(did)); let swept = 0;
for (let i = 0; i < dids.length; i += SWEEP_CONCURRENCY) { const batch = dids.slice(i, i + SWEEP_CONCURRENCY); await Promise.all( batch.map(async (did) => { try { await resolve(did); swept++; } catch { // resolveProfile logs and caches its own failures; one bad owner // must not abandon the rest of the sweep. } }), ); }
return swept;}
// An identity event means the handle changed now, so the TTL has to be bypassed —// otherwise this is a no-op until the row ages out on its own.//// UPDATE-only by way of hasCachedProfile: identity events arrive for every DID on// the network, not just ours, so an unknown DID must cost nothing beyond the lookup.export function refreshTrackedProfile( did: string, resolve: ProfileResolver = resolveProfile,): void { if (isDidBanned(did) || !hasCachedProfile(did)) return; expireCachedProfile(did); void resolve(did).catch(() => { // Already logged in resolveProfile; ingestion must not stop for one profile. });}