import { AtProtoClient, NetworkSlicesActorDefsProfileViewBasic, NetworkSlicesActorProfile, NetworkSlicesSlice, NetworkSlicesSliceDefsSliceView, NetworkSlicesSliceDefsSparklinePoint, NetworkSlicesSliceStatsOutput, } from "../client.ts"; import { recordBlobToCdnUrl, RecordResponse } from "@slices/client"; async function fetchSparklinesForSlices( client: AtProtoClient, sliceUris: string[] ): Promise> { if (sliceUris.length === 0) return {}; try { const sparklinesResponse = await client.network.slices.slice.getSparklines({ slices: sliceUris, duration: "24h", }); // Convert array of sparklineEntry objects to a map const sparklinesMap: Record = {}; for (const entry of sparklinesResponse.sparklines) { sparklinesMap[entry.sliceUri] = entry.points; } return sparklinesMap; } catch (error) { console.warn("Failed to fetch batch sparkline data:", error); } return {}; } async function fetchStatsForSlice( client: AtProtoClient, sliceUri: string ): Promise { try { const statsResponse = await client.network.slices.slice.stats({ slice: sliceUri, }); return statsResponse; } catch (error) { console.warn("Failed to fetch stats for slice:", sliceUri, error); } return null; } async function fetchStatsForSlices( client: AtProtoClient, sliceUris: string[] ): Promise> { const statsMap: Record = {}; // Fetch stats for each slice individually (no batch stats endpoint yet) await Promise.all( sliceUris.map(async (uri) => { statsMap[uri] = await fetchStatsForSlice(client, uri); }) ); return statsMap; } export async function getSlice( client: AtProtoClient, uri: string ): Promise { try { const sliceRecord = await client.network.slices.slice.getRecord({ uri }); if (!sliceRecord) return null; const creatorProfile = await getSliceActor(client, sliceRecord.did); if (!creatorProfile) return null; const [sparklinesMap, stats, requestCount, inviteCount] = await Promise.all([ fetchSparklinesForSlices(client, [uri]), fetchStatsForSlice(client, uri), client.network.slices.waitlist.request.countRecords({ where: { slice: { eq: uri } }, }).then(r => r.count).catch(() => 0), client.network.slices.waitlist.invite.countRecords({ where: { slice: { eq: uri } }, }).then(r => r.count).catch(() => 0), ]); const sparklineData = sparklinesMap[uri]; return sliceToView(sliceRecord, creatorProfile, sparklineData, stats, requestCount, inviteCount); } catch (error) { console.error("Failed to get slice:", error); return null; } } export async function getSliceActor( client: AtProtoClient, did: string ): Promise { try { const profileUri = `at://${did}/network.slices.actor.profile/self`; const profileRecord = await client.network.slices.actor.profile.getRecord({ uri: profileUri, }); if (!profileRecord) return null; // Get the actual handle using getActors const actorsResponse = await client.getActors({ where: { did: { eq: did } }, limit: 1, }); const handle = actorsResponse.actors.length > 0 ? actorsResponse.actors[0].handle ?? did : did; // fallback to DID if no handle found return actorToView(profileRecord, handle); } catch (error) { console.error("Failed to get slice actor:", error); return null; } } export function sliceToView( sliceRecord: RecordResponse, creator: NetworkSlicesActorDefsProfileViewBasic, sparkline?: NetworkSlicesSliceDefsSparklinePoint[], stats?: NetworkSlicesSliceStatsOutput | null, waitlistRequestCount?: number, waitlistInviteCount?: number ): NetworkSlicesSliceDefsSliceView { return { uri: sliceRecord.uri, cid: sliceRecord.cid, name: sliceRecord.value.name, domain: sliceRecord.value.domain, creator, createdAt: sliceRecord.value.createdAt, sparkline, indexedRecordCount: stats?.totalRecords || 0, indexedActorCount: stats?.totalActors || 0, indexedCollectionCount: stats?.collectionStats.length || 0, waitlistRequestCount: waitlistRequestCount || 0, waitlistInviteCount: waitlistInviteCount || 0, }; } export function actorToView( profileRecord: RecordResponse, handle: string ): NetworkSlicesActorDefsProfileViewBasic { return { did: profileRecord.did, handle, displayName: profileRecord.value.displayName, description: profileRecord.value.description, avatar: profileRecord.value.avatar ? recordBlobToCdnUrl(profileRecord, profileRecord.value.avatar, "avatar") : undefined, }; } export async function getSlicesForActor( client: AtProtoClient, did: string ): Promise { try { const slicesResponse = await client.network.slices.slice.getRecords({ where: { did: { eq: did }, }, sortBy: [{ field: "createdAt", direction: "desc" }], }); // Collect slice URIs for batch sparkline and stats fetch const sliceUris = slicesResponse.records.map((record) => record.uri); // Fetch sparklines and stats for all slices at once const [sparklinesMap, statsMap] = await Promise.all([ fetchSparklinesForSlices(client, sliceUris), fetchStatsForSlices(client, sliceUris), ]); const sliceViews: NetworkSlicesSliceDefsSliceView[] = []; for (const sliceRecord of slicesResponse.records) { const creator = await getSliceActor(client, sliceRecord.did); if (creator) { const sparklineData = sparklinesMap[sliceRecord.uri]; const statsData = statsMap[sliceRecord.uri]; sliceViews.push( sliceToView(sliceRecord, creator, sparklineData, statsData) ); } } return sliceViews; } catch (error) { console.error("Failed to get slices for actor:", error); return []; } } export async function searchSlices( client: AtProtoClient, query: string, limit = 20 ): Promise { try { const slicesResponse = await client.network.slices.slice.getRecords({ where: { name: { contains: query }, }, limit, }); // Collect slice URIs for batch sparkline and stats fetch const sliceUris = slicesResponse.records.map((record) => record.uri); // Fetch sparklines and stats for all slices at once const [sparklinesMap, statsMap] = await Promise.all([ fetchSparklinesForSlices(client, sliceUris), fetchStatsForSlices(client, sliceUris), ]); const sliceViews: NetworkSlicesSliceDefsSliceView[] = []; for (const sliceRecord of slicesResponse.records) { const creator = await getSliceActor(client, sliceRecord.did); if (creator) { const sparklineData = sparklinesMap[sliceRecord.uri]; const statsData = statsMap[sliceRecord.uri]; sliceViews.push( sliceToView(sliceRecord, creator, sparklineData, statsData) ); } } return sliceViews; } catch (error) { console.error("Failed to search slices:", error); return []; } } export async function getTimeline( client: AtProtoClient, limit = 50 ): Promise { try { const slicesResponse = await client.network.slices.slice.getRecords({ limit, sortBy: [{ field: "createdAt", direction: "desc" }], }); // Collect slice URIs for batch sparkline and stats fetch const sliceUris = slicesResponse.records.map((record) => record.uri); // Fetch sparklines and stats for all slices at once const [sparklinesMap, statsMap] = await Promise.all([ fetchSparklinesForSlices(client, sliceUris), fetchStatsForSlices(client, sliceUris), ]); const sliceViews: NetworkSlicesSliceDefsSliceView[] = []; for (const sliceRecord of slicesResponse.records) { const creator = await getSliceActor(client, sliceRecord.did); if (creator) { const sparklineData = sparklinesMap[sliceRecord.uri]; const statsData = statsMap[sliceRecord.uri]; sliceViews.push( sliceToView(sliceRecord, creator, sparklineData, statsData) ); } } return sliceViews; } catch (error) { console.error("Failed to get timeline:", error); return []; } }