atmosphere explorer pdsls.dev
atproto tool typescript

deduplicate lookups

juli.ee 381f3ed4 38df6c06

verified
Changed files
+85 -6
src
views
+85 -6
src/views/record.tsx
··· 1 1 import { Client, CredentialManager } from "@atcute/client"; 2 + import { DidDocument, getPdsEndpoint } from "@atcute/identity"; 2 3 import { lexiconDoc } from "@atcute/lexicon-doc"; 3 4 import { RecordValidator } from "@atcute/lexicon-doc/validations"; 4 - import { ResolvedSchema } from "@atcute/lexicon-resolver"; 5 + import { FailedLexiconResolutionError, ResolvedSchema } from "@atcute/lexicon-resolver"; 5 6 import { ActorIdentifier, is, Nsid } from "@atcute/lexicons"; 6 7 import { AtprotoDid, Did, isNsid } from "@atcute/lexicons/syntax"; 7 8 import { verifyRecord } from "@atcute/repo"; ··· 31 32 import { pds } from "../components/navbar.jsx"; 32 33 import { addNotification, removeNotification } from "../components/notification.jsx"; 33 34 import Tooltip from "../components/tooltip.jsx"; 34 - import { resolveLexiconAuthority, resolveLexiconSchema, resolvePDS } from "../utils/api.js"; 35 + import { 36 + didDocumentResolver, 37 + resolveLexiconAuthority, 38 + resolveLexiconSchema, 39 + resolvePDS, 40 + } from "../utils/api.js"; 35 41 import { AtUri, uriTemplates } from "../utils/templates.js"; 36 42 import { lexicons } from "../utils/types/lexicons.js"; 37 43 44 + const authorityCache = new Map<string, Promise<AtprotoDid>>(); 45 + const documentCache = new Map<string, Promise<DidDocument>>(); 46 + const schemaCache = new Map<string, Promise<unknown>>(); 47 + 48 + const getAuthoritySegment = (nsid: string): string => { 49 + const segments = nsid.split("."); 50 + return segments.slice(0, -1).join("."); 51 + }; 52 + 53 + const resolveSchema = async (authority: AtprotoDid, nsid: Nsid): Promise<unknown> => { 54 + const cacheKey = `${authority}:${nsid}`; 55 + 56 + let cachedSchema = schemaCache.get(cacheKey); 57 + if (cachedSchema) { 58 + return cachedSchema; 59 + } 60 + 61 + const schemaPromise = (async () => { 62 + let didDocPromise = documentCache.get(authority); 63 + if (!didDocPromise) { 64 + didDocPromise = didDocumentResolver.resolve(authority); 65 + documentCache.set(authority, didDocPromise); 66 + } 67 + 68 + const didDocument = await didDocPromise; 69 + 70 + const pdsEndpoint = getPdsEndpoint(didDocument); 71 + 72 + if (!pdsEndpoint) { 73 + throw new FailedLexiconResolutionError(nsid, { 74 + cause: new TypeError(`no pds service in did document; did=${authority}`), 75 + }); 76 + } 77 + 78 + const rpc = new Client({ handler: new CredentialManager({ service: pdsEndpoint }) }); 79 + const response = await rpc.get("com.atproto.repo.getRecord", { 80 + params: { 81 + repo: authority, 82 + collection: "com.atproto.lexicon.schema", 83 + rkey: nsid, 84 + }, 85 + }); 86 + 87 + if (!response.ok) { 88 + throw new Error(`got http ${response.status}`); 89 + } 90 + 91 + return response.data.value; 92 + })(); 93 + 94 + schemaCache.set(cacheKey, schemaPromise); 95 + 96 + try { 97 + return await schemaPromise; 98 + } catch (err) { 99 + schemaCache.delete(cacheKey); 100 + throw err; 101 + } 102 + }; 103 + 38 104 const extractRefs = (obj: any): Nsid[] => { 39 105 const refs: Set<string> = new Set(); 40 106 ··· 86 152 } 87 153 88 154 const fetchPromise = (async () => { 155 + let authority: AtprotoDid | undefined; 156 + const authoritySegment = getAuthoritySegment(nsid); 89 157 try { 90 - const authority = await resolveLexiconAuthority(nsid); 91 - const schema = await resolveLexiconSchema(authority, nsid); 158 + let authorityPromise = authorityCache.get(authoritySegment); 159 + if (!authorityPromise) { 160 + authorityPromise = resolveLexiconAuthority(nsid); 161 + authorityCache.set(authoritySegment, authorityPromise); 162 + } 92 163 93 - resolved.set(nsid, schema.rawSchema); 164 + authority = await authorityPromise; 165 + const schema = await resolveSchema(authority, nsid); 94 166 95 - const refs = extractRefs(schema.rawSchema); 167 + resolved.set(nsid, schema); 168 + 169 + const refs = extractRefs(schema); 96 170 97 171 if (refs.length > 0) { 98 172 await Promise.all( ··· 102 176 } catch (err) { 103 177 console.error(`Failed to resolve lexicon ${nsid}:`, err); 104 178 failed.add(nsid); 179 + authorityCache.delete(authoritySegment); 180 + if (authority) { 181 + documentCache.delete(authority); 182 + } 105 183 } finally { 106 184 inFlight.delete(nsid); 107 185 } ··· 180 258 } 181 259 182 260 const lexiconDocs = Object.fromEntries(resolved); 261 + console.log(lexiconDocs); 183 262 184 263 const validator = new RecordValidator(lexiconDocs, params.collection as Nsid); 185 264 validator.parse({