+85
-6
src/views/record.tsx
+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({