csr tangled client in solid-js
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270import { ok } from '@atcute/client';import type { Did, GenericUri, ResourceUri } from '@atcute/lexicons/syntax';
import { CONSTELLATION_SERVICE, STAR_COLLECTION } from './constants';import { getRpc } from './client';import { hydrateBacklinks } from './hydration';import { type BacklinkRecordRef, type HydratedRecord, type PaginatedResult, sortByCreatedAt, timestampFromTid, toNumberMap,} from './records';import { paginateStatefulRecords, sortStatefulRecordsByUpdatedAt, statefulRecordMatchesQuery, type StatefulHydratedRecord, type StatefulPageOptions,} from './stateful';
export const getBacklinksPage = async ( subject: GenericUri, source: string, limit: number, cursor?: string,): Promise<{ records: BacklinkRecordRef[]; cursor?: string; total?: number }> => { const constellation = getRpc(CONSTELLATION_SERVICE); const page = await ok( constellation.get('blue.microcosm.links.getBacklinks', { params: { subject, source, limit, ...(cursor ? { cursor } : {}), }, }), );
return page as { records: BacklinkRecordRef[]; cursor?: string; total?: number };};
const getBacklinkDidsPage = async ( subject: GenericUri, source: string, limit: number, cursor?: string,): Promise<{ linking_dids: Did[]; cursor?: string | null; total?: number }> => { const constellation = getRpc(CONSTELLATION_SERVICE); const page = await ok( constellation.get('blue.microcosm.links.getBacklinkDids', { params: { subject, source, limit, ...(cursor ? { cursor } : {}), }, }), );
return page as { linking_dids: Did[]; cursor?: string | null; total?: number };};
export const getBacklinkDids = async (subject: GenericUri, source: string): Promise<Did[]> => { const dids: Did[] = []; let cursor: string | null = null;
do { const page = await getBacklinkDidsPage(subject, source, 100, cursor ?? undefined); dids.push(...page.linking_dids); cursor = page.cursor ?? null; } while (cursor);
return dids;};
const findBacklinkRefByDidForSource = async ( subject: GenericUri, source: string, did: Did,): Promise<BacklinkRecordRef | undefined> => { let cursor: string | null = null;
do { const page = await getBacklinksPage(subject, source, 100, cursor ?? undefined); const ref = page.records.find((record) => record.did === did && record.collection === STAR_COLLECTION); if (ref) { return ref; } cursor = page.cursor ?? null; } while (cursor);};
export const findBacklinkRefByDid = async ( subject: GenericUri, sources: string[], did: Did,): Promise<BacklinkRecordRef | undefined> => { const refs = await Promise.all(sources.map((source) => findBacklinkRefByDidForSource(subject, source, did))); return refs.find((ref) => ref);};
export const getBacklinks = async (subject: GenericUri, source: string): Promise<BacklinkRecordRef[]> => { const records: BacklinkRecordRef[] = []; let cursor: string | null = null; const constellation = getRpc(CONSTELLATION_SERVICE);
do { const page: { records: BacklinkRecordRef[]; cursor?: string | null } = await ok( constellation.get('blue.microcosm.links.getBacklinks', { params: { subject, source, limit: 100, ...(cursor ? { cursor } : {}), }, }), ); records.push(...page.records); cursor = page.cursor ?? null; } while (cursor);
return records;};
export const latestBacklinkRefByRkey = (refs: BacklinkRecordRef[]): BacklinkRecordRef | undefined => [...refs].sort((left, right) => left.rkey.localeCompare(right.rkey)).at(-1);
export const getUniqueBacklinkRefs = async ( subject: GenericUri, source: string | string[],): Promise<BacklinkRecordRef[]> => Array.from( new Map( ( await Promise.all( (Array.isArray(source) ? source : [source]).map((entry) => getBacklinks(subject, entry)), ) ) .flat() .map((ref) => [`${ref.did}/${ref.collection}/${ref.rkey}`, ref] as const), ).values(), );
export const getLatestBacklinkTimestamp = async ( subject: GenericUri, source: string | string[],): Promise<number> => { try { return timestampFromTid(latestBacklinkRefByRkey(await getUniqueBacklinkRefs(subject, source))?.rkey); } catch { return 0; }};
const BACKLINK_BATCH_LIMIT = 50;
export const listBacklinkedRecordsPage = async <T extends { createdAt?: string }, State extends string>( subject: GenericUri, source: string | string[], options: StatefulPageOptions<State>, getState: (uri: ResourceUri) => Promise<State>, getUpdatedAt?: (record: StatefulHydratedRecord<T, State>) => Promise<number>, hydrateRefs: (refs: BacklinkRecordRef[]) => Promise<Array<HydratedRecord<T>>> = hydrateBacklinks<T>,): Promise<PaginatedResult<StatefulHydratedRecord<T, State>>> => { if (getUpdatedAt) { const hydrated = await hydrateRefs(await getUniqueBacklinkRefs(subject, source)); const numbers = toNumberMap(hydrated); const states = await Promise.all(hydrated.map((record) => getState(record.uri))); const items = hydrated.map((record, index) => ({ ...record, number: numbers.get(record.uri) ?? index + 1, state: states[index], })); const ordered = await sortStatefulRecordsByUpdatedAt(items, getUpdatedAt);
return paginateStatefulRecords(ordered, options); }
const requestedEnd = options.offset + options.limit; const items: Array<StatefulHydratedRecord<T, State>> = []; const sources = Array.isArray(source) ? source : [source]; const batchLimit = Math.min(BACKLINK_BATCH_LIMIT, Math.max(options.limit + 1, 1)); const cursors = new Map(sources.map((entry) => [entry, undefined as string | undefined])); const exhausted = new Set<string>(); const seenRefs = new Set<string>(); let matchingSeen = 0; let scannedSeen = 0; let total: number | undefined;
while (items.length <= options.limit) { const pages = await Promise.all( sources .filter((entry) => !exhausted.has(entry)) .map(async (entry) => ({ source: entry, page: await getBacklinksPage(subject, entry, batchLimit, cursors.get(entry)), })), );
if (pages.length === 0) { return { items, totalCount: matchingSeen, hasNext: false }; }
const refs = pages .flatMap(({ source: entry, page }) => { total = sources.length === 1 ? page.total ?? total : undefined; cursors.set(entry, page.cursor); if (!page.cursor) { exhausted.add(entry); } return page.records; }) .filter((ref) => { const key = `${ref.did}/${ref.collection}/${ref.rkey}`; if (seenRefs.has(key)) { return false; } seenRefs.add(key); return true; });
if (refs.length === 0) { if (exhausted.size === sources.length) { return { items, totalCount: matchingSeen, hasNext: false }; } continue; }
if (pages.every(({ page }) => page.records.length === 0)) { return { items, totalCount: matchingSeen, hasNext: false }; }
const hydrated = await hydrateRefs(refs); hydrated.sort(sortByCreatedAt).reverse(); const states = await Promise.all(hydrated.map((record) => getState(record.uri)));
for (let index = 0; index < hydrated.length; index += 1) { const record = hydrated[index]; const state = states[index]; const number = total ? Math.max(total - scannedSeen, 1) : scannedSeen + 1; scannedSeen += 1;
if (state !== options.state) { continue; }
if (!statefulRecordMatchesQuery(record.value, options.query)) { continue; }
if (matchingSeen >= options.offset && matchingSeen < requestedEnd + 1) { items.push({ ...record, number, state }); }
matchingSeen += 1; if (matchingSeen > requestedEnd) { return { items: items.slice(0, options.limit), hasNext: true }; } }
if (exhausted.size === sources.length) { return { items, totalCount: matchingSeen, hasNext: false }; } }
return { items: items.slice(0, options.limit), hasNext: true };};