@atproto/lex-resolver 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ export * from './lex-resolver.js';
2
+ export * from './lex-resolver-error.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,yBAAyB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./lex-resolver.js"), exports);
5
+ tslib_1.__exportStar(require("./lex-resolver-error.js"), exports);
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,4DAAiC;AACjC,kEAAuC","sourcesContent":["export * from './lex-resolver.js'\nexport * from './lex-resolver-error.js'\n"]}
@@ -0,0 +1,8 @@
1
+ import { NSID } from '@atproto/syntax';
2
+ export declare class LexResolverError extends Error {
3
+ readonly nsid: NSID;
4
+ readonly description: string;
5
+ name: string;
6
+ constructor(nsid: NSID, description?: string, options?: ErrorOptions);
7
+ }
8
+ //# sourceMappingURL=lex-resolver-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lex-resolver-error.d.ts","sourceRoot":"","sources":["../src/lex-resolver-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAEtC,qBAAa,gBAAiB,SAAQ,KAAK;aAIvB,IAAI,EAAE,IAAI;aACV,WAAW;IAJ7B,IAAI,SAAqB;gBAGP,IAAI,EAAE,IAAI,EACV,WAAW,SAAuC,EAClE,OAAO,CAAC,EAAE,YAAY;CAIzB"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LexResolverError = void 0;
4
+ class LexResolverError extends Error {
5
+ nsid;
6
+ description;
7
+ name = 'LexResolverError';
8
+ constructor(nsid, description = `Could not resolve Lexicon for NSID`, options) {
9
+ super(`${description} (${nsid})`, options);
10
+ this.nsid = nsid;
11
+ this.description = description;
12
+ }
13
+ }
14
+ exports.LexResolverError = LexResolverError;
15
+ //# sourceMappingURL=lex-resolver-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lex-resolver-error.js","sourceRoot":"","sources":["../src/lex-resolver-error.ts"],"names":[],"mappings":";;;AAEA,MAAa,gBAAiB,SAAQ,KAAK;IAIvB;IACA;IAJlB,IAAI,GAAG,kBAAkB,CAAA;IAEzB,YACkB,IAAU,EACV,cAAc,oCAAoC,EAClE,OAAsB;QAEtB,KAAK,CAAC,GAAG,WAAW,KAAK,IAAI,GAAG,EAAE,OAAO,CAAC,CAAA;QAJ1B,SAAI,GAAJ,IAAI,CAAM;QACV,gBAAW,GAAX,WAAW,CAAuC;IAIpE,CAAC;CACF;AAVD,4CAUC","sourcesContent":["import { NSID } from '@atproto/syntax'\n\nexport class LexResolverError extends Error {\n name = 'LexResolverError'\n\n constructor(\n public readonly nsid: NSID,\n public readonly description = `Could not resolve Lexicon for NSID`,\n options?: ErrorOptions,\n ) {\n super(`${description} (${nsid})`, options)\n }\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import { LexiconDocument } from '@atproto/lex-document';
2
+ import { AtUri, NSID } from '@atproto/syntax';
3
+ import { CreateDidResolverOptions, Did, DidResolver, ResolveDidOptions } from '@atproto-labs/did-resolver';
4
+ export type LexResolverOptions = CreateDidResolverOptions & {
5
+ didAuthority?: Did;
6
+ };
7
+ export { AtUri, NSID };
8
+ export type { LexiconDocument, ResolveDidOptions };
9
+ export declare class LexResolver {
10
+ protected readonly options: LexResolverOptions;
11
+ protected readonly didResolver: DidResolver<'plc' | 'web'>;
12
+ constructor(options: LexResolverOptions);
13
+ get(nsidStr: NSID | string, options?: ResolveDidOptions): Promise<{
14
+ uri: AtUri;
15
+ document: LexiconDocument;
16
+ }>;
17
+ resolve(nsidStr: NSID | string): Promise<AtUri>;
18
+ fetch(uriStr: AtUri | string, options?: ResolveDidOptions): Promise<LexiconDocument>;
19
+ }
20
+ //# sourceMappingURL=lex-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lex-resolver.d.ts","sourceRoot":"","sources":["../src/lex-resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAyB,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EACL,wBAAwB,EACxB,GAAG,EACH,WAAW,EACX,iBAAiB,EAIlB,MAAM,4BAA4B,CAAA;AAGnC,MAAM,MAAM,kBAAkB,GAAG,wBAAwB,GAAG;IAC1D,YAAY,CAAC,EAAE,GAAG,CAAA;CACnB,CAAA;AAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACtB,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,CAAA;AAElD,qBAAa,WAAW;IAGV,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,kBAAkB;IAF1D,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,KAAK,GAAG,KAAK,CAAC,CAAA;gBAE3B,OAAO,EAAE,kBAAkB;IAIpD,GAAG,CACP,OAAO,EAAE,IAAI,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC;QACT,GAAG,EAAE,KAAK,CAAA;QACV,QAAQ,EAAE,eAAe,CAAA;KAC1B,CAAC;IAMI,OAAO,CAAC,OAAO,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAe/C,KAAK,CACT,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,eAAe,CAAC;CAmE5B"}
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LexResolver = exports.NSID = exports.AtUri = void 0;
4
+ const promises_1 = require("node:dns/promises");
5
+ const lex_client_1 = require("@atproto/lex-client");
6
+ const lex_document_1 = require("@atproto/lex-document");
7
+ const syntax_1 = require("@atproto/syntax");
8
+ Object.defineProperty(exports, "AtUri", { enumerable: true, get: function () { return syntax_1.AtUri; } });
9
+ Object.defineProperty(exports, "NSID", { enumerable: true, get: function () { return syntax_1.NSID; } });
10
+ const did_resolver_1 = require("@atproto-labs/did-resolver");
11
+ const lex_resolver_error_js_1 = require("./lex-resolver-error.js");
12
+ class LexResolver {
13
+ options;
14
+ didResolver;
15
+ constructor(options) {
16
+ this.options = options;
17
+ this.didResolver = (0, did_resolver_1.createDidResolver)(options);
18
+ }
19
+ async get(nsidStr, options) {
20
+ const uri = await this.resolve(nsidStr);
21
+ const document = await this.fetch(uri, options);
22
+ return { uri, document };
23
+ }
24
+ async resolve(nsidStr) {
25
+ const nsid = syntax_1.NSID.from(nsidStr);
26
+ const did = this.options.didAuthority ??
27
+ (await resolveLexiconDidAuthority(nsid).catch((cause) => {
28
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to resolve DID authority for Lexicon`, { cause });
29
+ }));
30
+ return syntax_1.AtUri.make(did, 'com.atproto.lexicon.schema', nsid.toString());
31
+ }
32
+ async fetch(uriStr, options) {
33
+ const uri = typeof uriStr === 'string' ? new syntax_1.AtUri(uriStr) : uriStr;
34
+ const { did, nsid } = parseLexiconUri(uri);
35
+ if (this.options.didAuthority && this.options.didAuthority !== did) {
36
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `DID authority mismatch: expected ${this.options.didAuthority}, got ${did}`);
37
+ }
38
+ const didDocument = await this.didResolver
39
+ .resolve(did, options)
40
+ .catch((cause) => {
41
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to resolve DID document for ${uri}`, { cause });
42
+ });
43
+ let service;
44
+ try {
45
+ service = (0, did_resolver_1.extractPdsUrl)(didDocument);
46
+ }
47
+ catch (cause) {
48
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `No PDS service endpoint found in DID document for ${uri}`, { cause });
49
+ }
50
+ const agent = (0, lex_client_1.buildAgent)({
51
+ service,
52
+ fetch: this.options.fetch,
53
+ });
54
+ // TODO: use com.atproto.sync.getRecord and check signature using
55
+ // DID document key
56
+ const response = await new lex_client_1.Client(agent)
57
+ .getRecord('com.atproto.lexicon.schema', nsid.toString(), { repo: did })
58
+ .catch((cause) => {
59
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to fetch Lexicon document at ${uri}`, { cause });
60
+ });
61
+ const result = lex_document_1.lexiconDocumentSchema.validate(response.body.value);
62
+ if (!result.success) {
63
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Invalid Lexicon document at ${uri}`, {
64
+ cause: result.error,
65
+ });
66
+ }
67
+ const document = result.value;
68
+ if (document.id !== nsid.toString()) {
69
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Invalid document id "${document.id}" for ${uri}`);
70
+ }
71
+ return document;
72
+ }
73
+ }
74
+ exports.LexResolver = LexResolver;
75
+ function parseLexiconUri(uri) {
76
+ // Validate input URI
77
+ const nsid = syntax_1.NSID.from(uri.rkey);
78
+ const did = uri.host;
79
+ (0, did_resolver_1.assertDid)(did);
80
+ return { did, nsid };
81
+ }
82
+ async function resolveLexiconDidAuthority(nsid) {
83
+ try {
84
+ return await getDomainTxtDid(`_lexicon.${nsid.authority}`);
85
+ }
86
+ catch (cause) {
87
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to resolve lexicon DID authority`, { cause });
88
+ }
89
+ }
90
+ async function getDomainTxtDid(domain) {
91
+ return parseDnsResult(await (0, promises_1.resolveTxt)(domain));
92
+ }
93
+ function parseDnsResult(chunkedResults) {
94
+ const didDefs = chunkedResults
95
+ .map((chunks) => chunks.join(''))
96
+ .filter((i) => i.startsWith('did='));
97
+ if (didDefs.length === 1) {
98
+ const did = didDefs[0].slice(4);
99
+ (0, did_resolver_1.assertDid)(did);
100
+ return did;
101
+ }
102
+ throw didDefs.length > 1
103
+ ? new Error('Multiple DIDs found in DNS TXT records')
104
+ : new Error('No DID found in DNS TXT records');
105
+ }
106
+ //# sourceMappingURL=lex-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lex-resolver.js","sourceRoot":"","sources":["../src/lex-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolveTxt } from 'node:dns/promises'\nimport { Client, buildAgent } from '@atproto/lex-client'\nimport { LexiconDocument, lexiconDocumentSchema } from '@atproto/lex-document'\nimport { AtUri, NSID } from '@atproto/syntax'\nimport {\n CreateDidResolverOptions,\n Did,\n DidResolver,\n ResolveDidOptions,\n assertDid,\n createDidResolver,\n extractPdsUrl,\n} from '@atproto-labs/did-resolver'\nimport { LexResolverError } from './lex-resolver-error.js'\n\nexport type LexResolverOptions = CreateDidResolverOptions & {\n didAuthority?: Did\n}\n\nexport { AtUri, NSID }\nexport type { LexiconDocument, ResolveDidOptions }\n\nexport class LexResolver {\n protected readonly didResolver: DidResolver<'plc' | 'web'>\n\n constructor(protected readonly options: LexResolverOptions) {\n this.didResolver = createDidResolver(options)\n }\n\n async get(\n nsidStr: NSID | string,\n options?: ResolveDidOptions,\n ): Promise<{\n uri: AtUri\n document: LexiconDocument\n }> {\n const uri = await this.resolve(nsidStr)\n const document = await this.fetch(uri, options)\n return { uri, document }\n }\n\n async resolve(nsidStr: NSID | string): Promise<AtUri> {\n const nsid = NSID.from(nsidStr)\n const did =\n this.options.didAuthority ??\n (await resolveLexiconDidAuthority(nsid).catch((cause) => {\n throw new LexResolverError(\n nsid,\n `Failed to resolve DID authority for Lexicon`,\n { cause },\n )\n }))\n\n return AtUri.make(did, 'com.atproto.lexicon.schema', nsid.toString())\n }\n\n async fetch(\n uriStr: AtUri | string,\n options?: ResolveDidOptions,\n ): Promise<LexiconDocument> {\n const uri = typeof uriStr === 'string' ? new AtUri(uriStr) : uriStr\n const { did, nsid } = parseLexiconUri(uri)\n\n if (this.options.didAuthority && this.options.didAuthority !== did) {\n throw new LexResolverError(\n nsid,\n `DID authority mismatch: expected ${this.options.didAuthority}, got ${did}`,\n )\n }\n\n const didDocument = await this.didResolver\n .resolve(did, options)\n .catch((cause) => {\n throw new LexResolverError(\n nsid,\n `Failed to resolve DID document for ${uri}`,\n { cause },\n )\n })\n\n let service: URL\n try {\n service = extractPdsUrl(didDocument)\n } catch (cause) {\n throw new LexResolverError(\n nsid,\n `No PDS service endpoint found in DID document for ${uri}`,\n { cause },\n )\n }\n\n const agent = buildAgent({\n service,\n fetch: this.options.fetch,\n })\n\n // TODO: use com.atproto.sync.getRecord and check signature using\n // DID document key\n const response = await new Client(agent)\n .getRecord('com.atproto.lexicon.schema', nsid.toString(), { repo: did })\n .catch((cause) => {\n throw new LexResolverError(\n nsid,\n `Failed to fetch Lexicon document at ${uri}`,\n { cause },\n )\n })\n\n const result = lexiconDocumentSchema.validate(response.body.value)\n if (!result.success) {\n throw new LexResolverError(nsid, `Invalid Lexicon document at ${uri}`, {\n cause: result.error,\n })\n }\n\n const document = result.value\n\n if (document.id !== nsid.toString()) {\n throw new LexResolverError(\n nsid,\n `Invalid document id \"${document.id}\" for ${uri}`,\n )\n }\n\n return document\n }\n}\n\nfunction parseLexiconUri(uri: AtUri): {\n did: Did\n nsid: NSID\n} {\n // Validate input URI\n const nsid = NSID.from(uri.rkey)\n const did = uri.host\n assertDid(did)\n\n return { did, nsid }\n}\n\nasync function resolveLexiconDidAuthority(nsid: NSID): Promise<Did> {\n try {\n return await getDomainTxtDid(`_lexicon.${nsid.authority}`)\n } catch (cause) {\n throw new LexResolverError(\n nsid,\n `Failed to resolve lexicon DID authority`,\n { cause },\n )\n }\n}\n\nasync function getDomainTxtDid(domain: string): Promise<Did> {\n return parseDnsResult(await resolveTxt(domain))\n}\n\nfunction parseDnsResult(chunkedResults: string[][]): Did {\n const didDefs = chunkedResults\n .map((chunks) => chunks.join(''))\n .filter((i) => i.startsWith('did='))\n\n if (didDefs.length === 1) {\n const did = didDefs[0].slice(4)\n assertDid(did)\n return did\n }\n\n throw didDefs.length > 1\n ? new Error('Multiple DIDs found in DNS TXT records')\n : new Error('No DID found in DNS TXT records')\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@atproto/lex-resolver",
3
+ "version": "0.0.0",
4
+ "license": "MIT",
5
+ "description": "Lexicon document resolver utility for AT Lexicons",
6
+ "keywords": [
7
+ "atproto",
8
+ "lexicon",
9
+ "resolver",
10
+ "utility"
11
+ ],
12
+ "homepage": "https://atproto.com",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/bluesky-social/atproto",
16
+ "directory": "packages/lex/lex-resolver"
17
+ },
18
+ "files": [
19
+ "./src",
20
+ "./dist"
21
+ ],
22
+ "sideEffects": false,
23
+ "type": "commonjs",
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "import": "./dist/index.js",
29
+ "require": "./dist/index.js",
30
+ "types": "./dist/index.d.ts"
31
+ }
32
+ },
33
+ "dependencies": {
34
+ "@atproto-labs/did-resolver": "workspace:*",
35
+ "@atproto/lex-client": "workspace:*",
36
+ "@atproto/lex-document": "workspace:*",
37
+ "@atproto/syntax": "workspace:*",
38
+ "tslib": "^2.8.1"
39
+ },
40
+ "devDependencies": {},
41
+ "scripts": {
42
+ "build": "tsc --build tsconfig.build.json"
43
+ }
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './lex-resolver.js'
2
+ export * from './lex-resolver-error.js'
@@ -0,0 +1,13 @@
1
+ import { NSID } from '@atproto/syntax'
2
+
3
+ export class LexResolverError extends Error {
4
+ name = 'LexResolverError'
5
+
6
+ constructor(
7
+ public readonly nsid: NSID,
8
+ public readonly description = `Could not resolve Lexicon for NSID`,
9
+ options?: ErrorOptions,
10
+ ) {
11
+ super(`${description} (${nsid})`, options)
12
+ }
13
+ }
@@ -0,0 +1,171 @@
1
+ import { resolveTxt } from 'node:dns/promises'
2
+ import { Client, buildAgent } from '@atproto/lex-client'
3
+ import { LexiconDocument, lexiconDocumentSchema } from '@atproto/lex-document'
4
+ import { AtUri, NSID } from '@atproto/syntax'
5
+ import {
6
+ CreateDidResolverOptions,
7
+ Did,
8
+ DidResolver,
9
+ ResolveDidOptions,
10
+ assertDid,
11
+ createDidResolver,
12
+ extractPdsUrl,
13
+ } from '@atproto-labs/did-resolver'
14
+ import { LexResolverError } from './lex-resolver-error.js'
15
+
16
+ export type LexResolverOptions = CreateDidResolverOptions & {
17
+ didAuthority?: Did
18
+ }
19
+
20
+ export { AtUri, NSID }
21
+ export type { LexiconDocument, ResolveDidOptions }
22
+
23
+ export class LexResolver {
24
+ protected readonly didResolver: DidResolver<'plc' | 'web'>
25
+
26
+ constructor(protected readonly options: LexResolverOptions) {
27
+ this.didResolver = createDidResolver(options)
28
+ }
29
+
30
+ async get(
31
+ nsidStr: NSID | string,
32
+ options?: ResolveDidOptions,
33
+ ): Promise<{
34
+ uri: AtUri
35
+ document: LexiconDocument
36
+ }> {
37
+ const uri = await this.resolve(nsidStr)
38
+ const document = await this.fetch(uri, options)
39
+ return { uri, document }
40
+ }
41
+
42
+ async resolve(nsidStr: NSID | string): Promise<AtUri> {
43
+ const nsid = NSID.from(nsidStr)
44
+ const did =
45
+ this.options.didAuthority ??
46
+ (await resolveLexiconDidAuthority(nsid).catch((cause) => {
47
+ throw new LexResolverError(
48
+ nsid,
49
+ `Failed to resolve DID authority for Lexicon`,
50
+ { cause },
51
+ )
52
+ }))
53
+
54
+ return AtUri.make(did, 'com.atproto.lexicon.schema', nsid.toString())
55
+ }
56
+
57
+ async fetch(
58
+ uriStr: AtUri | string,
59
+ options?: ResolveDidOptions,
60
+ ): Promise<LexiconDocument> {
61
+ const uri = typeof uriStr === 'string' ? new AtUri(uriStr) : uriStr
62
+ const { did, nsid } = parseLexiconUri(uri)
63
+
64
+ if (this.options.didAuthority && this.options.didAuthority !== did) {
65
+ throw new LexResolverError(
66
+ nsid,
67
+ `DID authority mismatch: expected ${this.options.didAuthority}, got ${did}`,
68
+ )
69
+ }
70
+
71
+ const didDocument = await this.didResolver
72
+ .resolve(did, options)
73
+ .catch((cause) => {
74
+ throw new LexResolverError(
75
+ nsid,
76
+ `Failed to resolve DID document for ${uri}`,
77
+ { cause },
78
+ )
79
+ })
80
+
81
+ let service: URL
82
+ try {
83
+ service = extractPdsUrl(didDocument)
84
+ } catch (cause) {
85
+ throw new LexResolverError(
86
+ nsid,
87
+ `No PDS service endpoint found in DID document for ${uri}`,
88
+ { cause },
89
+ )
90
+ }
91
+
92
+ const agent = buildAgent({
93
+ service,
94
+ fetch: this.options.fetch,
95
+ })
96
+
97
+ // TODO: use com.atproto.sync.getRecord and check signature using
98
+ // DID document key
99
+ const response = await new Client(agent)
100
+ .getRecord('com.atproto.lexicon.schema', nsid.toString(), { repo: did })
101
+ .catch((cause) => {
102
+ throw new LexResolverError(
103
+ nsid,
104
+ `Failed to fetch Lexicon document at ${uri}`,
105
+ { cause },
106
+ )
107
+ })
108
+
109
+ const result = lexiconDocumentSchema.validate(response.body.value)
110
+ if (!result.success) {
111
+ throw new LexResolverError(nsid, `Invalid Lexicon document at ${uri}`, {
112
+ cause: result.error,
113
+ })
114
+ }
115
+
116
+ const document = result.value
117
+
118
+ if (document.id !== nsid.toString()) {
119
+ throw new LexResolverError(
120
+ nsid,
121
+ `Invalid document id "${document.id}" for ${uri}`,
122
+ )
123
+ }
124
+
125
+ return document
126
+ }
127
+ }
128
+
129
+ function parseLexiconUri(uri: AtUri): {
130
+ did: Did
131
+ nsid: NSID
132
+ } {
133
+ // Validate input URI
134
+ const nsid = NSID.from(uri.rkey)
135
+ const did = uri.host
136
+ assertDid(did)
137
+
138
+ return { did, nsid }
139
+ }
140
+
141
+ async function resolveLexiconDidAuthority(nsid: NSID): Promise<Did> {
142
+ try {
143
+ return await getDomainTxtDid(`_lexicon.${nsid.authority}`)
144
+ } catch (cause) {
145
+ throw new LexResolverError(
146
+ nsid,
147
+ `Failed to resolve lexicon DID authority`,
148
+ { cause },
149
+ )
150
+ }
151
+ }
152
+
153
+ async function getDomainTxtDid(domain: string): Promise<Did> {
154
+ return parseDnsResult(await resolveTxt(domain))
155
+ }
156
+
157
+ function parseDnsResult(chunkedResults: string[][]): Did {
158
+ const didDefs = chunkedResults
159
+ .map((chunks) => chunks.join(''))
160
+ .filter((i) => i.startsWith('did='))
161
+
162
+ if (didDefs.length === 1) {
163
+ const did = didDefs[0].slice(4)
164
+ assertDid(did)
165
+ return did
166
+ }
167
+
168
+ throw didDefs.length > 1
169
+ ? new Error('Multiple DIDs found in DNS TXT records')
170
+ : new Error('No DID found in DNS TXT records')
171
+ }