@atproto/lex-resolver 0.0.2 → 0.0.4

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/lex-resolver-error.d.ts +1 -0
  3. package/dist/lex-resolver-error.d.ts.map +1 -1
  4. package/dist/lex-resolver-error.js +4 -0
  5. package/dist/lex-resolver-error.js.map +1 -1
  6. package/dist/lex-resolver.d.ts +42 -7
  7. package/dist/lex-resolver.d.ts.map +1 -1
  8. package/dist/lex-resolver.js +112 -44
  9. package/dist/lex-resolver.js.map +1 -1
  10. package/dist/lexicons/com/atproto/sync/getRecord.d.ts +3 -0
  11. package/dist/lexicons/com/atproto/sync/getRecord.d.ts.map +1 -0
  12. package/dist/lexicons/com/atproto/sync/getRecord.defs.d.ts +32 -0
  13. package/dist/lexicons/com/atproto/sync/getRecord.defs.d.ts.map +1 -0
  14. package/dist/lexicons/com/atproto/sync/getRecord.defs.js +30 -0
  15. package/dist/lexicons/com/atproto/sync/getRecord.defs.js.map +1 -0
  16. package/dist/lexicons/com/atproto/sync/getRecord.js +10 -0
  17. package/dist/lexicons/com/atproto/sync/getRecord.js.map +1 -0
  18. package/dist/lexicons/com/atproto/sync.d.ts +2 -0
  19. package/dist/lexicons/com/atproto/sync.d.ts.map +1 -0
  20. package/dist/lexicons/com/atproto/sync.js +9 -0
  21. package/dist/lexicons/com/atproto/sync.js.map +1 -0
  22. package/dist/lexicons/com/atproto.d.ts +2 -0
  23. package/dist/lexicons/com/atproto.d.ts.map +1 -0
  24. package/dist/lexicons/com/atproto.js +9 -0
  25. package/dist/lexicons/com/atproto.js.map +1 -0
  26. package/dist/lexicons/com.d.ts +2 -0
  27. package/dist/lexicons/com.d.ts.map +1 -0
  28. package/dist/lexicons/com.js +9 -0
  29. package/dist/lexicons/com.js.map +1 -0
  30. package/package.json +18 -7
  31. package/src/lex-resolver-error.ts +7 -0
  32. package/src/lex-resolver.ts +197 -75
  33. package/src/lexicons/com/atproto/sync/getRecord.defs.ts +38 -0
  34. package/src/lexicons/com/atproto/sync/getRecord.ts +6 -0
  35. package/src/lexicons/com/atproto/sync.ts +5 -0
  36. package/src/lexicons/com/atproto.ts +5 -0
  37. package/src/lexicons/com.ts +5 -0
  38. package/tsconfig.build.json +13 -0
  39. package/tsconfig.json +7 -0
  40. package/tsconfig.tests.json +9 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,54 @@
1
+ # @atproto/lex-resolver
2
+
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4384](https://github.com/bluesky-social/atproto/pull/4384) [`d396de0`](https://github.com/bluesky-social/atproto/commit/d396de016d1d55d08cfad1dabd3ffd9eaeea76ea) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Verify the signature when fetching lexicon document from a repo
8
+
9
+ - [#4390](https://github.com/bluesky-social/atproto/pull/4390) [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Replace use of `CID` with `Cid`
10
+
11
+ - [#4383](https://github.com/bluesky-social/atproto/pull/4383) [`8012627`](https://github.com/bluesky-social/atproto/commit/8012627a1226cb2f1c753385ad2497b6b43ffd2e) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add hooks
12
+
13
+ - [#4383](https://github.com/bluesky-social/atproto/pull/4383) [`8012627`](https://github.com/bluesky-social/atproto/commit/8012627a1226cb2f1c753385ad2497b6b43ffd2e) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `LexResolverError.from(string)` utility
14
+
15
+ - [#4383](https://github.com/bluesky-social/atproto/pull/4383) [`8012627`](https://github.com/bluesky-social/atproto/commit/8012627a1226cb2f1c753385ad2497b6b43ffd2e) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove `didAuthority` option (Replaced by `hooks.onResolveAuthority`)
16
+
17
+ - [#4390](https://github.com/bluesky-social/atproto/pull/4390) [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Rename schema methods `validate`, `check` and `maybe` to `safeParse`, `matches` and `ifMatches` respectively.
18
+
19
+ - [#4397](https://github.com/bluesky-social/atproto/pull/4397) [`688f9d6`](https://github.com/bluesky-social/atproto/commit/688f9d67597ba96d6e9c4a4aec4d394d42f4cbf4) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `CHANGELOG.md` to npm package
20
+
21
+ - Updated dependencies [[`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`2d13d05`](https://github.com/bluesky-social/atproto/commit/2d13d05ab06576703742b1b638d2f243b6b2915f), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`a487ab8`](https://github.com/bluesky-social/atproto/commit/a487ab8afe8f18d00662e666049be8d28de2b57e), [`a487ab8`](https://github.com/bluesky-social/atproto/commit/a487ab8afe8f18d00662e666049be8d28de2b57e), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`bcae2b7`](https://github.com/bluesky-social/atproto/commit/bcae2b77b68da6dc2ec202651c8bf41fd5769f69), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`a487ab8`](https://github.com/bluesky-social/atproto/commit/a487ab8afe8f18d00662e666049be8d28de2b57e), [`a487ab8`](https://github.com/bluesky-social/atproto/commit/a487ab8afe8f18d00662e666049be8d28de2b57e), [`bcae2b7`](https://github.com/bluesky-social/atproto/commit/bcae2b77b68da6dc2ec202651c8bf41fd5769f69), [`03a2a4b`](https://github.com/bluesky-social/atproto/commit/03a2a4bb3814ced7ad1d4fe6c94b5348a3bbc097), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`bcae2b7`](https://github.com/bluesky-social/atproto/commit/bcae2b77b68da6dc2ec202651c8bf41fd5769f69), [`bcae2b7`](https://github.com/bluesky-social/atproto/commit/bcae2b77b68da6dc2ec202651c8bf41fd5769f69), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`d396de0`](https://github.com/bluesky-social/atproto/commit/d396de016d1d55d08cfad1dabd3ffd9eaeea76ea), [`a487ab8`](https://github.com/bluesky-social/atproto/commit/a487ab8afe8f18d00662e666049be8d28de2b57e), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`9f87ff3`](https://github.com/bluesky-social/atproto/commit/9f87ff3aa60090c8c38b6ce400cba6ceff5cd2e9), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba), [`688f9d6`](https://github.com/bluesky-social/atproto/commit/688f9d67597ba96d6e9c4a4aec4d394d42f4cbf4), [`1d445af`](https://github.com/bluesky-social/atproto/commit/1d445af2a7fc27eca5a45869b29266e6a2a7f3ba)]:
22
+ - @atproto/lex-schema@0.0.3
23
+ - @atproto/lex-document@0.0.4
24
+ - @atproto/lex-client@0.0.3
25
+ - @atproto/lex-data@0.0.2
26
+ - @atproto/syntax@0.4.2
27
+ - @atproto/crypto@0.4.5
28
+ - @atproto-labs/did-resolver@0.2.4
29
+
30
+ ## 0.0.3
31
+
32
+ ### Patch Changes
33
+
34
+ - Updated dependencies []:
35
+ - @atproto/lex-client@0.0.2
36
+ - @atproto/lex-document@0.0.3
37
+
38
+ ## 0.0.2
39
+
40
+ ### Patch Changes
41
+
42
+ - Updated dependencies [[`5ffd612`](https://github.com/bluesky-social/atproto/commit/5ffd6129909071e979c30f31266119865ab582b6)]:
43
+ - @atproto/lex-document@0.0.2
44
+ - @atproto/lex-client@0.0.1
45
+
46
+ ## 0.0.1
47
+
48
+ ### Patch Changes
49
+
50
+ - [#4371](https://github.com/bluesky-social/atproto/pull/4371) [`46550d6`](https://github.com/bluesky-social/atproto/commit/46550d6c1ffb298f57d54eb1904067b2df5a40af) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Release
51
+
52
+ - Updated dependencies [[`46550d6`](https://github.com/bluesky-social/atproto/commit/46550d6c1ffb298f57d54eb1904067b2df5a40af)]:
53
+ - @atproto/lex-client@0.0.1
54
+ - @atproto/lex-document@0.0.1
@@ -4,5 +4,6 @@ export declare class LexResolverError extends Error {
4
4
  readonly description: string;
5
5
  name: string;
6
6
  constructor(nsid: NSID, description?: string, options?: ErrorOptions);
7
+ static from(nsid: NSID | string, description?: string): LexResolverError;
7
8
  }
8
9
  //# sourceMappingURL=lex-resolver-error.d.ts.map
@@ -1 +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"}
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;IAKxB,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM;CAMtD"}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LexResolverError = void 0;
4
+ const syntax_1 = require("@atproto/syntax");
4
5
  class LexResolverError extends Error {
5
6
  nsid;
6
7
  description;
@@ -10,6 +11,9 @@ class LexResolverError extends Error {
10
11
  this.nsid = nsid;
11
12
  this.description = description;
12
13
  }
14
+ static from(nsid, description) {
15
+ return new LexResolverError(typeof nsid === 'string' ? syntax_1.NSID.from(nsid) : nsid, description);
16
+ }
13
17
  }
14
18
  exports.LexResolverError = LexResolverError;
15
19
  //# sourceMappingURL=lex-resolver-error.js.map
@@ -1 +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"]}
1
+ {"version":3,"file":"lex-resolver-error.js","sourceRoot":"","sources":["../src/lex-resolver-error.ts"],"names":[],"mappings":";;;AAAA,4CAAsC;AAEtC,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;IAED,MAAM,CAAC,IAAI,CAAC,IAAmB,EAAE,WAAoB;QACnD,OAAO,IAAI,gBAAgB,CACzB,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EACjD,WAAW,CACZ,CAAA;IACH,CAAC;CACF;AAjBD,4CAiBC","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 static from(nsid: NSID | string, description?: string) {\n return new LexResolverError(\n typeof nsid === 'string' ? NSID.from(nsid) : nsid,\n description,\n )\n }\n}\n"]}
@@ -1,20 +1,55 @@
1
+ import { Cid } from '@atproto/lex-data';
1
2
  import { LexiconDocument } from '@atproto/lex-document';
2
3
  import { AtUri, NSID } from '@atproto/syntax';
3
4
  import { CreateDidResolverOptions, Did, DidResolver, ResolveDidOptions } from '@atproto-labs/did-resolver';
5
+ export type LexResolverResult = {
6
+ uri: AtUri;
7
+ cid: Cid;
8
+ lexicon: LexiconDocument;
9
+ };
10
+ export type LexResolverFetchResult = {
11
+ cid: Cid;
12
+ lexicon: LexiconDocument;
13
+ };
14
+ type Awaitable<T> = T | Promise<T>;
15
+ export type LexResolverHooks = {
16
+ onResolveAuthority?(data: {
17
+ nsid: NSID;
18
+ }): Awaitable<void | Did>;
19
+ onResolveAuthorityResult?(data: {
20
+ nsid: NSID;
21
+ did: Did;
22
+ }): Awaitable<void>;
23
+ onResolveAuthorityError?(data: {
24
+ nsid: NSID;
25
+ err: unknown;
26
+ }): Awaitable<void>;
27
+ onFetch?(data: {
28
+ uri: AtUri;
29
+ }): Awaitable<void | LexResolverFetchResult>;
30
+ onFetchResult?(data: {
31
+ uri: AtUri;
32
+ cid: Cid;
33
+ lexicon: LexiconDocument;
34
+ }): Awaitable<void>;
35
+ onFetchError?(data: {
36
+ uri: AtUri;
37
+ err: unknown;
38
+ }): Awaitable<void>;
39
+ };
4
40
  export type LexResolverOptions = CreateDidResolverOptions & {
5
- didAuthority?: Did;
41
+ hooks?: LexResolverHooks;
6
42
  };
7
- export { AtUri, NSID };
43
+ export { AtUri, type Cid, NSID };
8
44
  export type { LexiconDocument, ResolveDidOptions };
9
45
  export declare class LexResolver {
10
46
  protected readonly options: LexResolverOptions;
11
47
  protected readonly didResolver: DidResolver<'plc' | 'web'>;
12
48
  constructor(options: LexResolverOptions);
13
- get(nsidStr: NSID | string, options?: ResolveDidOptions): Promise<{
14
- uri: AtUri;
15
- document: LexiconDocument;
16
- }>;
49
+ get(nsidStr: NSID | string, options?: ResolveDidOptions): Promise<LexResolverResult>;
17
50
  resolve(nsidStr: NSID | string): Promise<AtUri>;
18
- fetch(uriStr: AtUri | string, options?: ResolveDidOptions): Promise<LexiconDocument>;
51
+ protected resolveLexiconAuthority(nsid: NSID): Promise<Did>;
52
+ fetch(uriStr: AtUri | string, options?: ResolveDidOptions): Promise<LexResolverResult>;
53
+ protected fetchLexiconUri(uri: AtUri, options?: ResolveDidOptions): Promise<LexResolverFetchResult>;
19
54
  }
20
55
  //# sourceMappingURL=lex-resolver.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"lex-resolver.d.ts","sourceRoot":"","sources":["../src/lex-resolver.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AACvC,OAAO,EAAE,eAAe,EAAyB,MAAM,uBAAuB,CAAA;AAQ9E,OAAO,EAAE,KAAK,EAAE,IAAI,EAAc,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAEL,wBAAwB,EACxB,GAAG,EACH,WAAW,EACX,iBAAiB,EAIlB,MAAM,4BAA4B,CAAA;AAInC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,KAAK,CAAA;IACV,GAAG,EAAE,GAAG,CAAA;IACR,OAAO,EAAE,eAAe,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,GAAG,CAAA;IACR,OAAO,EAAE,eAAe,CAAA;CACzB,CAAA;AAED,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAElC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,kBAAkB,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC,IAAI,GAAG,GAAG,CAAC,CAAA;IAChE,wBAAwB,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,GAAG,EAAE,GAAG,CAAA;KAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC1E,uBAAuB,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAE7E,OAAO,CAAC,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,KAAK,CAAA;KAAE,GAAG,SAAS,CAAC,IAAI,GAAG,sBAAsB,CAAC,CAAA;IACxE,aAAa,CAAC,CAAC,IAAI,EAAE;QACnB,GAAG,EAAE,KAAK,CAAA;QACV,GAAG,EAAE,GAAG,CAAA;QACR,OAAO,EAAE,eAAe,CAAA;KACzB,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IACnB,YAAY,CAAC,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;CACnE,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,wBAAwB,GAAG;IAC1D,KAAK,CAAC,EAAE,gBAAgB,CAAA;CACzB,CAAA;AAED,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG,EAAE,IAAI,EAAE,CAAA;AAChC,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,iBAAiB,CAAC;IAKvB,OAAO,CAAC,OAAO,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;cAuBrC,uBAAuB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC;IAY3D,KAAK,CACT,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,iBAAiB,CAAC;cAmBb,eAAe,CAC7B,GAAG,EAAE,KAAK,EACV,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,sBAAsB,CAAC;CAqEnC"}
@@ -1,14 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LexResolver = exports.NSID = exports.AtUri = void 0;
4
+ const tslib_1 = require("tslib");
4
5
  const promises_1 = require("node:dns/promises");
6
+ const crypto = tslib_1.__importStar(require("@atproto/crypto"));
5
7
  const lex_client_1 = require("@atproto/lex-client");
6
8
  const lex_document_1 = require("@atproto/lex-document");
9
+ const repo_1 = require("@atproto/repo");
7
10
  const syntax_1 = require("@atproto/syntax");
8
11
  Object.defineProperty(exports, "AtUri", { enumerable: true, get: function () { return syntax_1.AtUri; } });
9
12
  Object.defineProperty(exports, "NSID", { enumerable: true, get: function () { return syntax_1.NSID; } });
10
13
  const did_resolver_1 = require("@atproto-labs/did-resolver");
11
14
  const lex_resolver_error_js_1 = require("./lex-resolver-error.js");
15
+ const com = tslib_1.__importStar(require("./lexicons/com.js"));
12
16
  class LexResolver {
13
17
  options;
14
18
  didResolver;
@@ -18,89 +22,153 @@ class LexResolver {
18
22
  }
19
23
  async get(nsidStr, options) {
20
24
  const uri = await this.resolve(nsidStr);
21
- const document = await this.fetch(uri, options);
22
- return { uri, document };
25
+ return this.fetch(uri, options);
23
26
  }
24
27
  async resolve(nsidStr) {
25
28
  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
+ const did = (await this.options.hooks?.onResolveAuthority?.({ nsid })) ??
30
+ (await this.resolveLexiconAuthority(nsid).then(async (did) => {
31
+ await this.options.hooks?.onResolveAuthorityResult?.({ nsid, did });
32
+ return did;
33
+ }, async (err) => {
34
+ await this.options.hooks?.onResolveAuthorityError?.({ nsid, err });
35
+ throw err;
29
36
  }));
30
37
  return syntax_1.AtUri.make(did, 'com.atproto.lexicon.schema', nsid.toString());
31
38
  }
39
+ // @TODO This class could be made compatible with browsers by making the
40
+ // following method abstract and/or by allowing the caller to inject a DNS
41
+ // resolver implementation (based on DNS-over-HTTPS or similar), instead of
42
+ // using the Node.js built-in resolver.
43
+ async resolveLexiconAuthority(nsid) {
44
+ try {
45
+ return await getDomainTxtDid(`_lexicon.${nsid.authority}`);
46
+ }
47
+ catch (cause) {
48
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to resolve lexicon DID authority for ${nsid}`, { cause });
49
+ }
50
+ }
32
51
  async fetch(uriStr, options) {
33
52
  const uri = typeof uriStr === 'string' ? new syntax_1.AtUri(uriStr) : uriStr;
53
+ const { lexicon, cid } = (await this.options.hooks?.onFetch?.({ uri })) ??
54
+ (await this.fetchLexiconUri(uri, options).then(async (res) => {
55
+ await this.options.hooks?.onFetchResult?.({ uri, ...res });
56
+ return res;
57
+ }, async (err) => {
58
+ await this.options.hooks?.onFetchError?.({ uri, err });
59
+ throw err;
60
+ }));
61
+ return { uri, cid, lexicon };
62
+ }
63
+ async fetchLexiconUri(uri, options) {
34
64
  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
65
+ const { pds, key } = await this.didResolver
39
66
  .resolve(did, options)
67
+ .then(did_resolver_1.extractAtprotoData)
40
68
  .catch((cause) => {
41
- throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to resolve DID document for ${uri}`, { cause });
69
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to resolve DID document for ${did}`, { cause });
42
70
  });
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 });
71
+ if (!key || !pds || !URL.canParse(pds.serviceEndpoint)) {
72
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `No atproto PDS service endpoint or signing key found in ${did} DID document`);
49
73
  }
50
74
  const agent = (0, lex_client_1.buildAgent)({
51
- service,
75
+ service: pds.serviceEndpoint,
52
76
  fetch: this.options.fetch,
53
77
  });
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 });
78
+ const collection = 'com.atproto.lexicon.schema';
79
+ const rkey = nsid.toString();
80
+ const { cid, record } = await (0, lex_client_1.xrpc)(agent, com.atproto.sync.getRecord, {
81
+ signal: options?.signal,
82
+ headers: options?.noCache ? { 'Cache-Control': 'no-cache' } : undefined,
83
+ params: { did, collection, rkey },
84
+ }).then(({ body: carBytes }) => {
85
+ return verifyRecordProof(carBytes, did, key, collection, rkey).catch((cause) => {
86
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to verify Lexicon record proof at ${uri}`, { cause });
87
+ });
88
+ }, (cause) => {
89
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to fetch Record ${uri}`, {
90
+ cause,
91
+ });
60
92
  });
61
- const result = lex_document_1.lexiconDocumentSchema.validate(response.body.value);
62
- if (!result.success) {
93
+ const validationResult = lex_document_1.lexiconDocumentSchema.safeParse(record);
94
+ if (!validationResult.success) {
63
95
  throw new lex_resolver_error_js_1.LexResolverError(nsid, `Invalid Lexicon document at ${uri}`, {
64
- cause: result.error,
96
+ cause: validationResult.error,
65
97
  });
66
98
  }
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}`);
99
+ const lexicon = validationResult.value;
100
+ if (lexicon.id !== uri.rkey) {
101
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `Invalid document id "${lexicon.id}" at ${uri}`);
70
102
  }
71
- return document;
103
+ return { lexicon, cid };
72
104
  }
73
105
  }
74
106
  exports.LexResolver = LexResolver;
75
107
  function parseLexiconUri(uri) {
76
108
  // Validate input URI
77
109
  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
110
  try {
84
- return await getDomainTxtDid(`_lexicon.${nsid.authority}`);
111
+ const did = uri.host;
112
+ (0, did_resolver_1.assertDid)(did);
113
+ return { did, nsid };
85
114
  }
86
115
  catch (cause) {
87
- throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to resolve lexicon DID authority`, { cause });
116
+ throw new lex_resolver_error_js_1.LexResolverError(nsid, `URI host is not a DID ${uri}`, { cause });
88
117
  }
89
118
  }
90
119
  async function getDomainTxtDid(domain) {
91
- return parseDnsResult(await (0, promises_1.resolveTxt)(domain));
92
- }
93
- function parseDnsResult(chunkedResults) {
94
- const didDefs = chunkedResults
120
+ const didLines = (await (0, promises_1.resolveTxt)(domain))
95
121
  .map((chunks) => chunks.join(''))
96
122
  .filter((i) => i.startsWith('did='));
97
- if (didDefs.length === 1) {
98
- const did = didDefs[0].slice(4);
123
+ if (didLines.length === 1) {
124
+ const did = didLines[0].slice(4);
99
125
  (0, did_resolver_1.assertDid)(did);
100
126
  return did;
101
127
  }
102
- throw didDefs.length > 1
128
+ throw didLines.length > 1
103
129
  ? new Error('Multiple DIDs found in DNS TXT records')
104
130
  : new Error('No DID found in DNS TXT records');
105
131
  }
132
+ async function verifyRecordProof(car, did, key, collection, rkey) {
133
+ const { root, blocks } = await (0, repo_1.readCarWithRoot)(car);
134
+ const blockstore = new repo_1.MemoryBlockstore(blocks);
135
+ const commit = await blockstore.readObj(root, repo_1.def.commit);
136
+ if (commit.did !== did) {
137
+ throw new Error(`Invalid repo did: ${commit.did}`);
138
+ }
139
+ const signingKey = getDidKeyFromMultibase(key);
140
+ const validSig = await (0, repo_1.verifyCommitSig)(commit, signingKey);
141
+ if (!validSig) {
142
+ throw new Error(`Invalid signature on commit: ${root.toString()}`);
143
+ }
144
+ const mst = repo_1.MST.load(blockstore, commit.data);
145
+ const cid = await mst.get(`${collection}/${rkey}`);
146
+ if (!cid)
147
+ throw new Error('Record not found in proof');
148
+ const record = await blockstore.readRecord(cid);
149
+ if (record?.$type !== collection) {
150
+ throw new Error(`Invalid record type: expected ${collection}, got ${record?.$type}`);
151
+ }
152
+ return { cid, record };
153
+ }
154
+ function getDidKeyFromMultibase(key) {
155
+ switch (key.type) {
156
+ case 'EcdsaSecp256r1VerificationKey2019': {
157
+ const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase);
158
+ return crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes);
159
+ }
160
+ case 'EcdsaSecp256k1VerificationKey2019': {
161
+ const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase);
162
+ return crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes);
163
+ }
164
+ case 'Multikey': {
165
+ const { jwtAlg, keyBytes } = crypto.parseMultikey(key.publicKeyMultibase);
166
+ return crypto.formatDidKey(jwtAlg, keyBytes);
167
+ }
168
+ default: {
169
+ // Should never happen
170
+ throw new Error(`Unsupported verification method type: ${key.type}`);
171
+ }
172
+ }
173
+ }
106
174
  //# sourceMappingURL=lex-resolver.js.map
@@ -1 +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"]}
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 * as crypto from '@atproto/crypto'\nimport { buildAgent, xrpc } from '@atproto/lex-client'\nimport { Cid } from '@atproto/lex-data'\nimport { LexiconDocument, lexiconDocumentSchema } from '@atproto/lex-document'\nimport {\n MST,\n MemoryBlockstore,\n def as repoDef,\n readCarWithRoot,\n verifyCommitSig,\n} from '@atproto/repo'\nimport { AtUri, NSID, NsidString } from '@atproto/syntax'\nimport {\n AtprotoVerificationMethod,\n CreateDidResolverOptions,\n Did,\n DidResolver,\n ResolveDidOptions,\n assertDid,\n createDidResolver,\n extractAtprotoData,\n} from '@atproto-labs/did-resolver'\nimport { LexResolverError } from './lex-resolver-error.js'\nimport * as com from './lexicons/com.js'\n\nexport type LexResolverResult = {\n uri: AtUri\n cid: Cid\n lexicon: LexiconDocument\n}\n\nexport type LexResolverFetchResult = {\n cid: Cid\n lexicon: LexiconDocument\n}\n\ntype Awaitable<T> = T | Promise<T>\n\nexport type LexResolverHooks = {\n onResolveAuthority?(data: { nsid: NSID }): Awaitable<void | Did>\n onResolveAuthorityResult?(data: { nsid: NSID; did: Did }): Awaitable<void>\n onResolveAuthorityError?(data: { nsid: NSID; err: unknown }): Awaitable<void>\n\n onFetch?(data: { uri: AtUri }): Awaitable<void | LexResolverFetchResult>\n onFetchResult?(data: {\n uri: AtUri\n cid: Cid\n lexicon: LexiconDocument\n }): Awaitable<void>\n onFetchError?(data: { uri: AtUri; err: unknown }): Awaitable<void>\n}\n\nexport type LexResolverOptions = CreateDidResolverOptions & {\n hooks?: LexResolverHooks\n}\n\nexport { AtUri, type Cid, 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<LexResolverResult> {\n const uri = await this.resolve(nsidStr)\n return this.fetch(uri, options)\n }\n\n async resolve(nsidStr: NSID | string): Promise<AtUri> {\n const nsid = NSID.from(nsidStr)\n\n const did =\n (await this.options.hooks?.onResolveAuthority?.({ nsid })) ??\n (await this.resolveLexiconAuthority(nsid).then(\n async (did) => {\n await this.options.hooks?.onResolveAuthorityResult?.({ nsid, did })\n return did\n },\n async (err) => {\n await this.options.hooks?.onResolveAuthorityError?.({ nsid, err })\n throw err\n },\n ))\n\n return AtUri.make(did, 'com.atproto.lexicon.schema', nsid.toString())\n }\n\n // @TODO This class could be made compatible with browsers by making the\n // following method abstract and/or by allowing the caller to inject a DNS\n // resolver implementation (based on DNS-over-HTTPS or similar), instead of\n // using the Node.js built-in resolver.\n protected async resolveLexiconAuthority(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 for ${nsid}`,\n { cause },\n )\n }\n }\n\n async fetch(\n uriStr: AtUri | string,\n options?: ResolveDidOptions,\n ): Promise<LexResolverResult> {\n const uri = typeof uriStr === 'string' ? new AtUri(uriStr) : uriStr\n\n const { lexicon, cid } =\n (await this.options.hooks?.onFetch?.({ uri })) ??\n (await this.fetchLexiconUri(uri, options).then(\n async (res) => {\n await this.options.hooks?.onFetchResult?.({ uri, ...res })\n return res\n },\n async (err) => {\n await this.options.hooks?.onFetchError?.({ uri, err })\n throw err\n },\n ))\n\n return { uri, cid, lexicon }\n }\n\n protected async fetchLexiconUri(\n uri: AtUri,\n options?: ResolveDidOptions,\n ): Promise<LexResolverFetchResult> {\n const { did, nsid } = parseLexiconUri(uri)\n\n const { pds, key } = await this.didResolver\n .resolve(did, options)\n .then(extractAtprotoData)\n .catch((cause) => {\n throw new LexResolverError(\n nsid,\n `Failed to resolve DID document for ${did}`,\n { cause },\n )\n })\n\n if (!key || !pds || !URL.canParse(pds.serviceEndpoint)) {\n throw new LexResolverError(\n nsid,\n `No atproto PDS service endpoint or signing key found in ${did} DID document`,\n )\n }\n\n const agent = buildAgent({\n service: pds.serviceEndpoint,\n fetch: this.options.fetch,\n })\n\n const collection = 'com.atproto.lexicon.schema'\n const rkey = nsid.toString()\n\n const { cid, record } = await xrpc(agent, com.atproto.sync.getRecord, {\n signal: options?.signal,\n headers: options?.noCache ? { 'Cache-Control': 'no-cache' } : undefined,\n params: { did, collection, rkey },\n }).then(\n ({ body: carBytes }) => {\n return verifyRecordProof(carBytes, did, key, collection, rkey).catch(\n (cause) => {\n throw new LexResolverError(\n nsid,\n `Failed to verify Lexicon record proof at ${uri}`,\n { cause },\n )\n },\n )\n },\n (cause) => {\n throw new LexResolverError(nsid, `Failed to fetch Record ${uri}`, {\n cause,\n })\n },\n )\n\n const validationResult = lexiconDocumentSchema.safeParse(record)\n if (!validationResult.success) {\n throw new LexResolverError(nsid, `Invalid Lexicon document at ${uri}`, {\n cause: validationResult.error,\n })\n }\n\n const lexicon = validationResult.value\n if (lexicon.id !== uri.rkey) {\n throw new LexResolverError(\n nsid,\n `Invalid document id \"${lexicon.id}\" at ${uri}`,\n )\n }\n\n return { lexicon, cid }\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 try {\n const did = uri.host\n assertDid(did)\n return { did, nsid }\n } catch (cause) {\n throw new LexResolverError(nsid, `URI host is not a DID ${uri}`, { cause })\n }\n}\n\nasync function getDomainTxtDid(domain: string): Promise<Did> {\n const didLines = (await resolveTxt(domain))\n .map((chunks) => chunks.join(''))\n .filter((i) => i.startsWith('did='))\n\n if (didLines.length === 1) {\n const did = didLines[0].slice(4)\n assertDid(did)\n return did\n }\n\n throw didLines.length > 1\n ? new Error('Multiple DIDs found in DNS TXT records')\n : new Error('No DID found in DNS TXT records')\n}\n\nasync function verifyRecordProof(\n car: Uint8Array,\n did: Did,\n key: AtprotoVerificationMethod,\n collection: NsidString,\n rkey: string,\n) {\n const { root, blocks } = await readCarWithRoot(car)\n const blockstore = new MemoryBlockstore(blocks)\n\n const commit = await blockstore.readObj(root, repoDef.commit)\n if (commit.did !== did) {\n throw new Error(`Invalid repo did: ${commit.did}`)\n }\n\n const signingKey = getDidKeyFromMultibase(key)\n const validSig = await verifyCommitSig(commit, signingKey)\n if (!validSig) {\n throw new Error(`Invalid signature on commit: ${root.toString()}`)\n }\n\n const mst = MST.load(blockstore, commit.data)\n\n const cid = await mst.get(`${collection}/${rkey}`)\n if (!cid) throw new Error('Record not found in proof')\n\n const record = await blockstore.readRecord(cid)\n if (record?.$type !== collection) {\n throw new Error(\n `Invalid record type: expected ${collection}, got ${record?.$type}`,\n )\n }\n\n return { cid, record }\n}\n\nfunction getDidKeyFromMultibase(key: AtprotoVerificationMethod) {\n switch (key.type) {\n case 'EcdsaSecp256r1VerificationKey2019': {\n const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)\n return crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes)\n }\n case 'EcdsaSecp256k1VerificationKey2019': {\n const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)\n return crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes)\n }\n case 'Multikey': {\n const { jwtAlg, keyBytes } = crypto.parseMultikey(key.publicKeyMultibase)\n return crypto.formatDidKey(jwtAlg, keyBytes)\n }\n default: {\n // Should never happen\n throw new Error(`Unsupported verification method type: ${key.type}`)\n }\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export * from './getRecord.defs.js';
2
+ export * as $defs from './getRecord.defs.js';
3
+ //# sourceMappingURL=getRecord.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRecord.d.ts","sourceRoot":"","sources":["../../../../../src/lexicons/com/atproto/sync/getRecord.ts"],"names":[],"mappings":"AAIA,cAAc,qBAAqB,CAAA;AACnC,OAAO,KAAK,KAAK,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1,32 @@
1
+ import { l } from '@atproto/lex-schema';
2
+ declare const $nsid = "com.atproto.sync.getRecord";
3
+ export { $nsid };
4
+ /**
5
+ * Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth.
6
+ */
7
+ declare const main: l.Query<"com.atproto.sync.getRecord", l.ParamsSchema<{
8
+ readonly did: l.StringSchema<{
9
+ readonly format: "did";
10
+ }>;
11
+ readonly collection: l.StringSchema<{
12
+ readonly format: "nsid";
13
+ }>;
14
+ readonly rkey: l.StringSchema<{
15
+ readonly format: "record-key";
16
+ }>;
17
+ }>, l.Payload<"application/vnd.ipld.car", undefined>, readonly ["RecordNotFound", "RepoNotFound", "RepoTakendown", "RepoSuspended", "RepoDeactivated"]>;
18
+ export { main };
19
+ export declare const $params: l.ParamsSchema<{
20
+ readonly did: l.StringSchema<{
21
+ readonly format: "did";
22
+ }>;
23
+ readonly collection: l.StringSchema<{
24
+ readonly format: "nsid";
25
+ }>;
26
+ readonly rkey: l.StringSchema<{
27
+ readonly format: "record-key";
28
+ }>;
29
+ }>, $output: l.Payload<"application/vnd.ipld.car", undefined>;
30
+ export type Params = l.InferQueryParameters<typeof main>;
31
+ export type Output = l.InferQueryOutputBody<typeof main>;
32
+ //# sourceMappingURL=getRecord.defs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRecord.defs.d.ts","sourceRoot":"","sources":["../../../../../src/lexicons/com/atproto/sync/getRecord.defs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,qBAAqB,CAAA;AAEvC,QAAA,MAAM,KAAK,+BAA+B,CAAA;AAE1C,OAAO,EAAE,KAAK,EAAE,CAAA;AAEhB;;GAEG;AACH,QAAA,MAAM,IAAI;;;;;;;;;;uJAiBP,CAAA;AACH,OAAO,EAAE,IAAI,EAAE,CAAA;AAEf,eAAO,MAAM,OAAO;;;;;;;;;;EAAkB,EACpC,OAAO,kDAAc,CAAA;AAEvB,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,oBAAoB,CAAC,OAAO,IAAI,CAAC,CAAA;AACxD,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,oBAAoB,CAAC,OAAO,IAAI,CAAC,CAAA"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ /*
3
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.$output = exports.$params = exports.main = exports.$nsid = void 0;
7
+ const lex_schema_1 = require("@atproto/lex-schema");
8
+ const $nsid = 'com.atproto.sync.getRecord';
9
+ exports.$nsid = $nsid;
10
+ /**
11
+ * Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth.
12
+ */
13
+ const main =
14
+ /*#__PURE__*/
15
+ lex_schema_1.l.query($nsid,
16
+ /*#__PURE__*/ lex_schema_1.l.params({
17
+ did: /*#__PURE__*/ lex_schema_1.l.string({ format: 'did' }),
18
+ collection: /*#__PURE__*/ lex_schema_1.l.string({ format: 'nsid' }),
19
+ rkey: /*#__PURE__*/ lex_schema_1.l.string({ format: 'record-key' }),
20
+ }),
21
+ /*#__PURE__*/ lex_schema_1.l.payload('application/vnd.ipld.car'), [
22
+ 'RecordNotFound',
23
+ 'RepoNotFound',
24
+ 'RepoTakendown',
25
+ 'RepoSuspended',
26
+ 'RepoDeactivated',
27
+ ]);
28
+ exports.main = main;
29
+ exports.$params = main.parameters, exports.$output = main.output;
30
+ //# sourceMappingURL=getRecord.defs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRecord.defs.js","sourceRoot":"","sources":["../../../../../src/lexicons/com/atproto/sync/getRecord.defs.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,oDAAuC;AAEvC,MAAM,KAAK,GAAG,4BAA4B,CAAA;AAEjC,sBAAK;AAEd;;GAEG;AACH,MAAM,IAAI;AACR,aAAa;AACb,cAAC,CAAC,KAAK,CACL,KAAK;AACL,aAAa,CAAC,cAAC,CAAC,MAAM,CAAC;IACrB,GAAG,EAAE,aAAa,CAAC,cAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC9C,UAAU,EAAE,aAAa,CAAC,cAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACtD,IAAI,EAAE,aAAa,CAAC,cAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;CACvD,CAAC;AACF,aAAa,CAAC,cAAC,CAAC,OAAO,CAAC,0BAA0B,CAAC,EACnD;IACE,gBAAgB;IAChB,cAAc;IACd,eAAe;IACf,eAAe;IACf,iBAAiB;CAClB,CACF,CAAA;AACM,oBAAI;AAEA,QAAA,OAAO,GAAG,IAAI,CAAC,UAAU,EACpC,QAAA,OAAO,GAAG,IAAI,CAAC,MAAM,CAAA","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nimport { l } from '@atproto/lex-schema'\n\nconst $nsid = 'com.atproto.sync.getRecord'\n\nexport { $nsid }\n\n/**\n * Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth.\n */\nconst main =\n /*#__PURE__*/\n l.query(\n $nsid,\n /*#__PURE__*/ l.params({\n did: /*#__PURE__*/ l.string({ format: 'did' }),\n collection: /*#__PURE__*/ l.string({ format: 'nsid' }),\n rkey: /*#__PURE__*/ l.string({ format: 'record-key' }),\n }),\n /*#__PURE__*/ l.payload('application/vnd.ipld.car'),\n [\n 'RecordNotFound',\n 'RepoNotFound',\n 'RepoTakendown',\n 'RepoSuspended',\n 'RepoDeactivated',\n ],\n )\nexport { main }\n\nexport const $params = main.parameters,\n $output = main.output\n\nexport type Params = l.InferQueryParameters<typeof main>\nexport type Output = l.InferQueryOutputBody<typeof main>\n"]}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /*
3
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.$defs = void 0;
7
+ const tslib_1 = require("tslib");
8
+ tslib_1.__exportStar(require("./getRecord.defs.js"), exports);
9
+ exports.$defs = tslib_1.__importStar(require("./getRecord.defs.js"));
10
+ //# sourceMappingURL=getRecord.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRecord.js","sourceRoot":"","sources":["../../../../../src/lexicons/com/atproto/sync/getRecord.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;AAEH,8DAAmC;AACnC,qEAA4C","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nexport * from './getRecord.defs.js'\nexport * as $defs from './getRecord.defs.js'\n"]}
@@ -0,0 +1,2 @@
1
+ export * as getRecord from './sync/getRecord.js';
2
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/lexicons/com/atproto/sync.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,SAAS,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /*
3
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getRecord = void 0;
7
+ const tslib_1 = require("tslib");
8
+ exports.getRecord = tslib_1.__importStar(require("./sync/getRecord.js"));
9
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../../src/lexicons/com/atproto/sync.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;AAEH,yEAAgD","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nexport * as getRecord from './sync/getRecord.js'\n"]}
@@ -0,0 +1,2 @@
1
+ export * as sync from './atproto/sync.js';
2
+ //# sourceMappingURL=atproto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atproto.d.ts","sourceRoot":"","sources":["../../../src/lexicons/com/atproto.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /*
3
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sync = void 0;
7
+ const tslib_1 = require("tslib");
8
+ exports.sync = tslib_1.__importStar(require("./atproto/sync.js"));
9
+ //# sourceMappingURL=atproto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atproto.js","sourceRoot":"","sources":["../../../src/lexicons/com/atproto.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;AAEH,kEAAyC","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nexport * as sync from './atproto/sync.js'\n"]}
@@ -0,0 +1,2 @@
1
+ export * as atproto from './com/atproto.js';
2
+ //# sourceMappingURL=com.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"com.d.ts","sourceRoot":"","sources":["../../src/lexicons/com.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /*
3
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.atproto = void 0;
7
+ const tslib_1 = require("tslib");
8
+ exports.atproto = tslib_1.__importStar(require("./com/atproto.js"));
9
+ //# sourceMappingURL=com.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"com.js","sourceRoot":"","sources":["../../src/lexicons/com.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;AAEH,oEAA2C","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nexport * as atproto from './com/atproto.js'\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/lex-resolver",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "license": "MIT",
5
5
  "description": "Lexicon document resolver utility for AT Lexicons",
6
6
  "keywords": [
@@ -17,7 +17,11 @@
17
17
  },
18
18
  "files": [
19
19
  "./src",
20
- "./dist"
20
+ "./tsconfig.build.json",
21
+ "./tsconfig.tests.json",
22
+ "./tsconfig.json",
23
+ "./dist",
24
+ "./CHANGELOG.md"
21
25
  ],
22
26
  "sideEffects": false,
23
27
  "type": "commonjs",
@@ -32,13 +36,20 @@
32
36
  },
33
37
  "dependencies": {
34
38
  "tslib": "^2.8.1",
35
- "@atproto-labs/did-resolver": "0.2.3",
36
- "@atproto/lex-client": "0.0.1",
37
- "@atproto/lex-document": "0.0.2",
38
- "@atproto/syntax": "0.4.1"
39
+ "@atproto-labs/did-resolver": "0.2.4",
40
+ "@atproto/crypto": "0.4.5",
41
+ "@atproto/lex-client": "0.0.3",
42
+ "@atproto/lex-data": "0.0.2",
43
+ "@atproto/lex-document": "0.0.4",
44
+ "@atproto/lex-schema": "0.0.3",
45
+ "@atproto/repo": "0.8.11",
46
+ "@atproto/syntax": "0.4.2"
47
+ },
48
+ "devDependencies": {
49
+ "@atproto/lex-builder": "0.0.5"
39
50
  },
40
- "devDependencies": {},
41
51
  "scripts": {
52
+ "prebuild": "node ./scripts/lex-build.mjs",
42
53
  "build": "tsc --build tsconfig.build.json"
43
54
  }
44
55
  }
@@ -10,4 +10,11 @@ export class LexResolverError extends Error {
10
10
  ) {
11
11
  super(`${description} (${nsid})`, options)
12
12
  }
13
+
14
+ static from(nsid: NSID | string, description?: string) {
15
+ return new LexResolverError(
16
+ typeof nsid === 'string' ? NSID.from(nsid) : nsid,
17
+ description,
18
+ )
19
+ }
13
20
  }
@@ -1,23 +1,61 @@
1
1
  import { resolveTxt } from 'node:dns/promises'
2
- import { Client, buildAgent } from '@atproto/lex-client'
2
+ import * as crypto from '@atproto/crypto'
3
+ import { buildAgent, xrpc } from '@atproto/lex-client'
4
+ import { Cid } from '@atproto/lex-data'
3
5
  import { LexiconDocument, lexiconDocumentSchema } from '@atproto/lex-document'
4
- import { AtUri, NSID } from '@atproto/syntax'
5
6
  import {
7
+ MST,
8
+ MemoryBlockstore,
9
+ def as repoDef,
10
+ readCarWithRoot,
11
+ verifyCommitSig,
12
+ } from '@atproto/repo'
13
+ import { AtUri, NSID, NsidString } from '@atproto/syntax'
14
+ import {
15
+ AtprotoVerificationMethod,
6
16
  CreateDidResolverOptions,
7
17
  Did,
8
18
  DidResolver,
9
19
  ResolveDidOptions,
10
20
  assertDid,
11
21
  createDidResolver,
12
- extractPdsUrl,
22
+ extractAtprotoData,
13
23
  } from '@atproto-labs/did-resolver'
14
24
  import { LexResolverError } from './lex-resolver-error.js'
25
+ import * as com from './lexicons/com.js'
26
+
27
+ export type LexResolverResult = {
28
+ uri: AtUri
29
+ cid: Cid
30
+ lexicon: LexiconDocument
31
+ }
32
+
33
+ export type LexResolverFetchResult = {
34
+ cid: Cid
35
+ lexicon: LexiconDocument
36
+ }
37
+
38
+ type Awaitable<T> = T | Promise<T>
39
+
40
+ export type LexResolverHooks = {
41
+ onResolveAuthority?(data: { nsid: NSID }): Awaitable<void | Did>
42
+ onResolveAuthorityResult?(data: { nsid: NSID; did: Did }): Awaitable<void>
43
+ onResolveAuthorityError?(data: { nsid: NSID; err: unknown }): Awaitable<void>
44
+
45
+ onFetch?(data: { uri: AtUri }): Awaitable<void | LexResolverFetchResult>
46
+ onFetchResult?(data: {
47
+ uri: AtUri
48
+ cid: Cid
49
+ lexicon: LexiconDocument
50
+ }): Awaitable<void>
51
+ onFetchError?(data: { uri: AtUri; err: unknown }): Awaitable<void>
52
+ }
15
53
 
16
54
  export type LexResolverOptions = CreateDidResolverOptions & {
17
- didAuthority?: Did
55
+ hooks?: LexResolverHooks
18
56
  }
19
57
 
20
- export { AtUri, NSID }
58
+ export { AtUri, type Cid, NSID }
21
59
  export type { LexiconDocument, ResolveDidOptions }
22
60
 
23
61
  export class LexResolver {
@@ -30,99 +68,139 @@ export class LexResolver {
30
68
  async get(
31
69
  nsidStr: NSID | string,
32
70
  options?: ResolveDidOptions,
33
- ): Promise<{
34
- uri: AtUri
35
- document: LexiconDocument
36
- }> {
71
+ ): Promise<LexResolverResult> {
37
72
  const uri = await this.resolve(nsidStr)
38
- const document = await this.fetch(uri, options)
39
- return { uri, document }
73
+ return this.fetch(uri, options)
40
74
  }
41
75
 
42
76
  async resolve(nsidStr: NSID | string): Promise<AtUri> {
43
77
  const nsid = NSID.from(nsidStr)
78
+
44
79
  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
- }))
80
+ (await this.options.hooks?.onResolveAuthority?.({ nsid })) ??
81
+ (await this.resolveLexiconAuthority(nsid).then(
82
+ async (did) => {
83
+ await this.options.hooks?.onResolveAuthorityResult?.({ nsid, did })
84
+ return did
85
+ },
86
+ async (err) => {
87
+ await this.options.hooks?.onResolveAuthorityError?.({ nsid, err })
88
+ throw err
89
+ },
90
+ ))
53
91
 
54
92
  return AtUri.make(did, 'com.atproto.lexicon.schema', nsid.toString())
55
93
  }
56
94
 
95
+ // @TODO This class could be made compatible with browsers by making the
96
+ // following method abstract and/or by allowing the caller to inject a DNS
97
+ // resolver implementation (based on DNS-over-HTTPS or similar), instead of
98
+ // using the Node.js built-in resolver.
99
+ protected async resolveLexiconAuthority(nsid: NSID): Promise<Did> {
100
+ try {
101
+ return await getDomainTxtDid(`_lexicon.${nsid.authority}`)
102
+ } catch (cause) {
103
+ throw new LexResolverError(
104
+ nsid,
105
+ `Failed to resolve lexicon DID authority for ${nsid}`,
106
+ { cause },
107
+ )
108
+ }
109
+ }
110
+
57
111
  async fetch(
58
112
  uriStr: AtUri | string,
59
113
  options?: ResolveDidOptions,
60
- ): Promise<LexiconDocument> {
114
+ ): Promise<LexResolverResult> {
61
115
  const uri = typeof uriStr === 'string' ? new AtUri(uriStr) : uriStr
62
- const { did, nsid } = parseLexiconUri(uri)
63
116
 
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
- }
117
+ const { lexicon, cid } =
118
+ (await this.options.hooks?.onFetch?.({ uri })) ??
119
+ (await this.fetchLexiconUri(uri, options).then(
120
+ async (res) => {
121
+ await this.options.hooks?.onFetchResult?.({ uri, ...res })
122
+ return res
123
+ },
124
+ async (err) => {
125
+ await this.options.hooks?.onFetchError?.({ uri, err })
126
+ throw err
127
+ },
128
+ ))
129
+
130
+ return { uri, cid, lexicon }
131
+ }
70
132
 
71
- const didDocument = await this.didResolver
133
+ protected async fetchLexiconUri(
134
+ uri: AtUri,
135
+ options?: ResolveDidOptions,
136
+ ): Promise<LexResolverFetchResult> {
137
+ const { did, nsid } = parseLexiconUri(uri)
138
+
139
+ const { pds, key } = await this.didResolver
72
140
  .resolve(did, options)
141
+ .then(extractAtprotoData)
73
142
  .catch((cause) => {
74
143
  throw new LexResolverError(
75
144
  nsid,
76
- `Failed to resolve DID document for ${uri}`,
145
+ `Failed to resolve DID document for ${did}`,
77
146
  { cause },
78
147
  )
79
148
  })
80
149
 
81
- let service: URL
82
- try {
83
- service = extractPdsUrl(didDocument)
84
- } catch (cause) {
150
+ if (!key || !pds || !URL.canParse(pds.serviceEndpoint)) {
85
151
  throw new LexResolverError(
86
152
  nsid,
87
- `No PDS service endpoint found in DID document for ${uri}`,
88
- { cause },
153
+ `No atproto PDS service endpoint or signing key found in ${did} DID document`,
89
154
  )
90
155
  }
91
156
 
92
157
  const agent = buildAgent({
93
- service,
158
+ service: pds.serviceEndpoint,
94
159
  fetch: this.options.fetch,
95
160
  })
96
161
 
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 },
162
+ const collection = 'com.atproto.lexicon.schema'
163
+ const rkey = nsid.toString()
164
+
165
+ const { cid, record } = await xrpc(agent, com.atproto.sync.getRecord, {
166
+ signal: options?.signal,
167
+ headers: options?.noCache ? { 'Cache-Control': 'no-cache' } : undefined,
168
+ params: { did, collection, rkey },
169
+ }).then(
170
+ ({ body: carBytes }) => {
171
+ return verifyRecordProof(carBytes, did, key, collection, rkey).catch(
172
+ (cause) => {
173
+ throw new LexResolverError(
174
+ nsid,
175
+ `Failed to verify Lexicon record proof at ${uri}`,
176
+ { cause },
177
+ )
178
+ },
106
179
  )
107
- })
180
+ },
181
+ (cause) => {
182
+ throw new LexResolverError(nsid, `Failed to fetch Record ${uri}`, {
183
+ cause,
184
+ })
185
+ },
186
+ )
108
187
 
109
- const result = lexiconDocumentSchema.validate(response.body.value)
110
- if (!result.success) {
188
+ const validationResult = lexiconDocumentSchema.safeParse(record)
189
+ if (!validationResult.success) {
111
190
  throw new LexResolverError(nsid, `Invalid Lexicon document at ${uri}`, {
112
- cause: result.error,
191
+ cause: validationResult.error,
113
192
  })
114
193
  }
115
194
 
116
- const document = result.value
117
-
118
- if (document.id !== nsid.toString()) {
195
+ const lexicon = validationResult.value
196
+ if (lexicon.id !== uri.rkey) {
119
197
  throw new LexResolverError(
120
198
  nsid,
121
- `Invalid document id "${document.id}" for ${uri}`,
199
+ `Invalid document id "${lexicon.id}" at ${uri}`,
122
200
  )
123
201
  }
124
202
 
125
- return document
203
+ return { lexicon, cid }
126
204
  }
127
205
  }
128
206
 
@@ -132,40 +210,84 @@ function parseLexiconUri(uri: AtUri): {
132
210
  } {
133
211
  // Validate input URI
134
212
  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
213
  try {
143
- return await getDomainTxtDid(`_lexicon.${nsid.authority}`)
214
+ const did = uri.host
215
+ assertDid(did)
216
+ return { did, nsid }
144
217
  } catch (cause) {
145
- throw new LexResolverError(
146
- nsid,
147
- `Failed to resolve lexicon DID authority`,
148
- { cause },
149
- )
218
+ throw new LexResolverError(nsid, `URI host is not a DID ${uri}`, { cause })
150
219
  }
151
220
  }
152
221
 
153
222
  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
223
+ const didLines = (await resolveTxt(domain))
159
224
  .map((chunks) => chunks.join(''))
160
225
  .filter((i) => i.startsWith('did='))
161
226
 
162
- if (didDefs.length === 1) {
163
- const did = didDefs[0].slice(4)
227
+ if (didLines.length === 1) {
228
+ const did = didLines[0].slice(4)
164
229
  assertDid(did)
165
230
  return did
166
231
  }
167
232
 
168
- throw didDefs.length > 1
233
+ throw didLines.length > 1
169
234
  ? new Error('Multiple DIDs found in DNS TXT records')
170
235
  : new Error('No DID found in DNS TXT records')
171
236
  }
237
+
238
+ async function verifyRecordProof(
239
+ car: Uint8Array,
240
+ did: Did,
241
+ key: AtprotoVerificationMethod,
242
+ collection: NsidString,
243
+ rkey: string,
244
+ ) {
245
+ const { root, blocks } = await readCarWithRoot(car)
246
+ const blockstore = new MemoryBlockstore(blocks)
247
+
248
+ const commit = await blockstore.readObj(root, repoDef.commit)
249
+ if (commit.did !== did) {
250
+ throw new Error(`Invalid repo did: ${commit.did}`)
251
+ }
252
+
253
+ const signingKey = getDidKeyFromMultibase(key)
254
+ const validSig = await verifyCommitSig(commit, signingKey)
255
+ if (!validSig) {
256
+ throw new Error(`Invalid signature on commit: ${root.toString()}`)
257
+ }
258
+
259
+ const mst = MST.load(blockstore, commit.data)
260
+
261
+ const cid = await mst.get(`${collection}/${rkey}`)
262
+ if (!cid) throw new Error('Record not found in proof')
263
+
264
+ const record = await blockstore.readRecord(cid)
265
+ if (record?.$type !== collection) {
266
+ throw new Error(
267
+ `Invalid record type: expected ${collection}, got ${record?.$type}`,
268
+ )
269
+ }
270
+
271
+ return { cid, record }
272
+ }
273
+
274
+ function getDidKeyFromMultibase(key: AtprotoVerificationMethod) {
275
+ switch (key.type) {
276
+ case 'EcdsaSecp256r1VerificationKey2019': {
277
+ const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)
278
+ return crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes)
279
+ }
280
+ case 'EcdsaSecp256k1VerificationKey2019': {
281
+ const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)
282
+ return crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes)
283
+ }
284
+ case 'Multikey': {
285
+ const { jwtAlg, keyBytes } = crypto.parseMultikey(key.publicKeyMultibase)
286
+ return crypto.formatDidKey(jwtAlg, keyBytes)
287
+ }
288
+ default: {
289
+ // Should never happen
290
+ throw new Error(`Unsupported verification method type: ${key.type}`)
291
+ }
292
+ }
293
+ }
@@ -0,0 +1,38 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ import { l } from '@atproto/lex-schema'
6
+
7
+ const $nsid = 'com.atproto.sync.getRecord'
8
+
9
+ export { $nsid }
10
+
11
+ /**
12
+ * Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth.
13
+ */
14
+ const main =
15
+ /*#__PURE__*/
16
+ l.query(
17
+ $nsid,
18
+ /*#__PURE__*/ l.params({
19
+ did: /*#__PURE__*/ l.string({ format: 'did' }),
20
+ collection: /*#__PURE__*/ l.string({ format: 'nsid' }),
21
+ rkey: /*#__PURE__*/ l.string({ format: 'record-key' }),
22
+ }),
23
+ /*#__PURE__*/ l.payload('application/vnd.ipld.car'),
24
+ [
25
+ 'RecordNotFound',
26
+ 'RepoNotFound',
27
+ 'RepoTakendown',
28
+ 'RepoSuspended',
29
+ 'RepoDeactivated',
30
+ ],
31
+ )
32
+ export { main }
33
+
34
+ export const $params = main.parameters,
35
+ $output = main.output
36
+
37
+ export type Params = l.InferQueryParameters<typeof main>
38
+ export type Output = l.InferQueryOutputBody<typeof main>
@@ -0,0 +1,6 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ export * from './getRecord.defs.js'
6
+ export * as $defs from './getRecord.defs.js'
@@ -0,0 +1,5 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ export * as getRecord from './sync/getRecord.js'
@@ -0,0 +1,5 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ export * as sync from './atproto/sync.js'
@@ -0,0 +1,5 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ export * as atproto from './com/atproto.js'
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": ["../../../tsconfig/node.json"],
3
+ "include": ["./src"],
4
+ "exclude": ["**/*.test.ts"],
5
+ "compilerOptions": {
6
+ "noImplicitAny": true,
7
+ "importHelpers": true,
8
+ "target": "ES2023",
9
+ "rootDir": "./src",
10
+ "outDir": "./dist",
11
+ "types": ["node"]
12
+ }
13
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "include": [],
3
+ "references": [
4
+ { "path": "./tsconfig.build.json" },
5
+ { "path": "./tsconfig.tests.json" }
6
+ ]
7
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../../tsconfig/tests.json",
3
+ "include": ["./tests", "./src/**.test.ts"],
4
+ "compilerOptions": {
5
+ "noImplicitAny": true,
6
+ "rootDir": "./",
7
+ "baseUrl": "./"
8
+ }
9
+ }