import { useEffect, useState } from 'react'; import { useDidResolution } from './useDidResolution'; import { usePdsEndpoint } from './usePdsEndpoint'; import { createAtprotoClient } from '../utils/atproto-client'; /** * Shape of the state returned by {@link useLatestRecord}. */ export interface LatestRecordState { /** Latest record value if one exists. */ record?: T; /** Record key for the fetched record, when derivable. */ rkey?: string; /** Error encountered while fetching. */ error?: Error; /** Indicates whether a fetch is in progress. */ loading: boolean; /** `true` when the collection has zero records. */ empty: boolean; } /** * Fetches the most recent record from a collection using `listRecords(limit=1)`. * * @param handleOrDid - Handle or DID that owns the collection. * @param collection - NSID of the collection to query. * @returns {LatestRecordState} Object reporting the latest record value, derived rkey, loading status, emptiness, and any error. */ export function useLatestRecord(handleOrDid: string | undefined, collection: string): LatestRecordState { const { did, error: didError, loading: resolvingDid } = useDidResolution(handleOrDid); const { endpoint, error: endpointError, loading: resolvingEndpoint } = usePdsEndpoint(did); const [state, setState] = useState>({ loading: !!handleOrDid, empty: false }); useEffect(() => { let cancelled = false; const assign = (next: Partial>) => { if (cancelled) return; setState(prev => ({ ...prev, ...next })); }; if (!handleOrDid) { assign({ loading: false, record: undefined, rkey: undefined, error: undefined, empty: false }); return () => { cancelled = true; }; } if (didError) { assign({ loading: false, error: didError, empty: false }); return () => { cancelled = true; }; } if (endpointError) { assign({ loading: false, error: endpointError, empty: false }); return () => { cancelled = true; }; } if (resolvingDid || resolvingEndpoint || !did || !endpoint) { assign({ loading: true, error: undefined }); return () => { cancelled = true; }; } assign({ loading: true, error: undefined, empty: false }); (async () => { try { const { rpc } = await createAtprotoClient({ service: endpoint }); const res = await (rpc as unknown as { get: ( nsid: string, opts: { params: Record } ) => Promise<{ ok: boolean; data: { records: Array<{ uri: string; rkey?: string; value: T }> } }>; }).get('com.atproto.repo.listRecords', { params: { repo: did, collection, limit: 1, reverse: false } }); if (!res.ok) throw new Error('Failed to list records'); const list = res.data.records; if (list.length === 0) { assign({ loading: false, empty: true, record: undefined, rkey: undefined }); return; } const first = list[0]; const derivedRkey = first.rkey ?? extractRkey(first.uri); assign({ record: first.value, rkey: derivedRkey, loading: false, empty: false }); } catch (e) { assign({ error: e as Error, loading: false, empty: false }); } })(); return () => { cancelled = true; }; }, [handleOrDid, did, endpoint, collection, resolvingDid, resolvingEndpoint, didError, endpointError]); return state; } function extractRkey(uri: string): string | undefined { if (!uri) return undefined; const parts = uri.split('/'); return parts[parts.length - 1]; }