@atproto/lexicon-resolver 0.4.3 → 0.4.5

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @atproto/lexicon-resolver
2
2
 
3
+ ## 0.4.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update TypeScript build to rely on references to composite internal projects
8
+
9
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bundle only necessary files in the NPM tarball, including the `CHANGELOG.md` and `README.md` files (if present).
10
+
11
+ - Updated dependencies [[`28a0b58`](https://github.com/bluesky-social/atproto/commit/28a0b588147863eaef948cd2bb8fc0f19d08cda9), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07)]:
12
+ - @atproto/syntax@0.6.4
13
+ - @atproto/identity@0.5.3
14
+ - @atproto-labs/fetch-node@0.3.4
15
+ - @atproto/lex-document@0.1.3
16
+ - @atproto/lex@0.1.7
17
+ - @atproto/repo@0.10.3
18
+
19
+ ## 0.4.4
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies []:
24
+ - @atproto/lex@0.1.6
25
+
3
26
  ## 0.4.3
4
27
 
5
28
  ### Patch Changes
package/package.json CHANGED
@@ -1,10 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/lexicon-resolver",
3
- "version": "0.4.3",
4
- "engines": {
5
- "node": ">=22"
6
- },
7
- "type": "module",
3
+ "version": "0.4.5",
8
4
  "license": "MIT",
9
5
  "description": "ATProto Lexicon resolution",
10
6
  "keywords": [
@@ -17,28 +13,37 @@
17
13
  "url": "https://github.com/bluesky-social/atproto",
18
14
  "directory": "packages/lexicon-resolver"
19
15
  },
16
+ "files": [
17
+ "./dist",
18
+ "./README.md",
19
+ "./CHANGELOG.md"
20
+ ],
21
+ "type": "module",
20
22
  "exports": {
21
23
  ".": {
22
24
  "types": "./dist/index.d.ts",
23
25
  "default": "./dist/index.js"
24
26
  }
25
27
  },
28
+ "engines": {
29
+ "node": ">=22"
30
+ },
26
31
  "dependencies": {
27
- "@atproto-labs/fetch-node": "^0.3.3",
28
- "@atproto/identity": "^0.5.2",
29
- "@atproto/lex": "^0.1.5",
30
- "@atproto/lex-document": "^0.1.2",
31
- "@atproto/repo": "^0.10.2",
32
- "@atproto/syntax": "^0.6.3"
32
+ "@atproto-labs/fetch-node": "^0.3.4",
33
+ "@atproto/repo": "^0.10.3",
34
+ "@atproto/identity": "^0.5.3",
35
+ "@atproto/lex-document": "^0.1.3",
36
+ "@atproto/lex": "^0.1.7",
37
+ "@atproto/syntax": "^0.6.4"
33
38
  },
34
39
  "devDependencies": {
35
40
  "vitest": "^4.0.16",
36
- "@atproto/lex-cbor": "^0.1.2"
41
+ "@atproto/lex-cbor": "^0.1.3"
37
42
  },
38
43
  "scripts": {
39
44
  "test": "vitest run",
40
45
  "build": "tsgo --build tsconfig.build.json",
41
- "prebuild": "pnpm run codegen",
42
- "codegen": "lex build --clear --indexFile --lexicons ../../lexicons --include com.atproto.sync.getRecord --include com.atproto.lexicon.schema"
46
+ "prebuild": "pnpm run '/^(codegen:.+)$/'",
47
+ "codegen:lex": "lex build --clear --indexFile --lexicons ../../lexicons --include com.atproto.sync.getRecord --include com.atproto.lexicon.schema"
43
48
  }
44
49
  }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from './record.js'
2
- export * from './lexicon.js'
package/src/lexicon.ts DELETED
@@ -1,164 +0,0 @@
1
- import * as dns from 'node:dns/promises'
2
- import { Cid, l } from '@atproto/lex'
3
- import { LexiconDocument, lexiconDocumentSchema } from '@atproto/lex-document'
4
- import { Commit } from '@atproto/repo'
5
- import { AtUri, DidString, NSID, NsidString } from '@atproto/syntax'
6
- import * as lexiconsSchema from './lexicons/com/atproto/lexicon/schema.js'
7
- import {
8
- BuildRecordResolverOptions,
9
- ResolveRecordOptions,
10
- buildRecordResolver,
11
- } from './record.js'
12
-
13
- const DNS_SUBDOMAIN = '_lexicon'
14
- const DNS_PREFIX = 'did='
15
-
16
- export type LexiconDocumentRecord = lexiconsSchema.Main & LexiconDocument
17
- export const LEXICON_SCHEMA_NSID = lexiconsSchema.$nsid
18
-
19
- /**
20
- * Resolve Lexicon from an NSID
21
- */
22
- export type LexiconResolver = (
23
- nsid: NSID | NsidString,
24
- ) => Promise<LexiconResolution>
25
-
26
- /**
27
- * Resolve Lexicon from an NSID using Lexicon DID authority and record resolution
28
- */
29
- export type AtprotoLexiconResolver = (
30
- nsid: NSID | NsidString,
31
- options?: ResolveLexiconOptions,
32
- ) => Promise<LexiconResolution>
33
-
34
- export type BuildLexiconResolverOptions = BuildRecordResolverOptions
35
-
36
- export type ResolveLexiconOptions = ResolveRecordOptions & {
37
- didAuthority?: DidString
38
- }
39
-
40
- export type LexiconResolution = {
41
- commit: Commit
42
- uri: AtUri
43
- cid: Cid
44
- nsid: NSID
45
- lexicon: LexiconDocumentRecord
46
- }
47
-
48
- export { AtUri, NSID }
49
- export type { Cid, Commit, DidString, LexiconDocument, NsidString }
50
-
51
- /**
52
- * Build a Lexicon resolver function.
53
- */
54
- export function buildLexiconResolver(
55
- options: BuildLexiconResolverOptions = {},
56
- ): AtprotoLexiconResolver {
57
- const resolveRecord = buildRecordResolver(options)
58
- return async function (
59
- input: NSID | NsidString,
60
- opts: ResolveLexiconOptions = {},
61
- ): Promise<LexiconResolution> {
62
- const nsid = NSID.from(input)
63
- const didAuthority = await getDidAuthority(nsid, opts)
64
- const verified = await resolveRecord(
65
- AtUri.make(didAuthority, lexiconsSchema.$nsid, nsid.toString()),
66
- { forceRefresh: opts.forceRefresh },
67
- ).catch((err) => {
68
- throw new LexiconResolutionError(
69
- nsid,
70
- 'Could not resolve Lexicon schema record',
71
- { cause: err },
72
- )
73
- })
74
-
75
- if (!lexiconsSchema.$matches(verified.record)) {
76
- throw new LexiconResolutionError(nsid, 'Invalid Lexicon schema record')
77
- }
78
-
79
- const validationResult = lexiconDocumentSchema.safeValidate(verified.record)
80
- if (!validationResult.success) {
81
- throw new LexiconResolutionError(nsid, 'Invalid Lexicon document', {
82
- cause: validationResult.reason,
83
- })
84
- }
85
-
86
- const lexicon = validationResult.value
87
- if (lexicon.id !== nsid.toString()) {
88
- throw new LexiconResolutionError(
89
- nsid,
90
- `Lexicon schema record id (${lexicon.id}) does not match NSID`,
91
- )
92
- }
93
- const { uri, cid, commit } = verified
94
- return { commit, uri, cid, nsid, lexicon }
95
- } satisfies LexiconResolver
96
- }
97
-
98
- export const resolveLexicon = buildLexiconResolver()
99
-
100
- /**
101
- * Resolve the DID authority for a Lexicon from the network using DNS, based on its NSID.
102
- * @param input NSID or string representing one for which to lookup its Lexicon DID authority.
103
- */
104
- export async function resolveLexiconDidAuthority(
105
- input: NSID | NsidString,
106
- ): Promise<DidString | undefined> {
107
- const nsid = NSID.from(input)
108
- const did = await resolveDns(nsid.authority)
109
- if (did == null || !l.isDidString(did)) return
110
- return did
111
- }
112
-
113
- export class LexiconResolutionError extends Error {
114
- constructor(
115
- public readonly nsid: NSID,
116
- public readonly description = `Could not resolve Lexicon for NSID`,
117
- options?: ErrorOptions,
118
- ) {
119
- super(`${description} (${nsid})`, options)
120
- this.name = 'LexiconResolutionError'
121
- }
122
-
123
- static from(
124
- input: NSID | string,
125
- description?: string,
126
- options?: ErrorOptions,
127
- ): LexiconResolutionError {
128
- const nsid = NSID.from(input)
129
- return new LexiconResolutionError(nsid, description, options)
130
- }
131
- }
132
-
133
- async function getDidAuthority(nsid: NSID, options: ResolveLexiconOptions) {
134
- if (options.didAuthority) {
135
- return options.didAuthority
136
- }
137
- const did = await resolveLexiconDidAuthority(nsid)
138
- if (!did) {
139
- throw new LexiconResolutionError(
140
- nsid,
141
- `Could not resolve a DID authority for NSID`,
142
- )
143
- }
144
- return did
145
- }
146
-
147
- async function resolveDns(authority: string): Promise<string | undefined> {
148
- let chunkedResults: string[][]
149
- try {
150
- chunkedResults = await dns.resolveTxt(`${DNS_SUBDOMAIN}.${authority}`)
151
- } catch (err) {
152
- return undefined
153
- }
154
- return parseDnsResult(chunkedResults)
155
- }
156
-
157
- function parseDnsResult(chunkedResults: string[][]): string | undefined {
158
- const results = chunkedResults.map((chunks) => chunks.join(''))
159
- const found = results.filter((i) => i.startsWith(DNS_PREFIX))
160
- if (found.length !== 1) {
161
- return undefined
162
- }
163
- return found[0].slice(DNS_PREFIX.length)
164
- }
package/src/record.ts DELETED
@@ -1,184 +0,0 @@
1
- import { IdResolver, parseToAtprotoDocument } from '@atproto/identity'
2
- import {
3
- AgentConfig,
4
- Cid,
5
- Client,
6
- DidString,
7
- FetchHandler,
8
- LexMap,
9
- l,
10
- } from '@atproto/lex'
11
- import {
12
- Commit,
13
- MST,
14
- MemoryBlockstore,
15
- def as repoDef,
16
- readCarWithRoot,
17
- verifyCommitSig,
18
- } from '@atproto/repo'
19
- import { AtUri, AtUriString } from '@atproto/syntax'
20
- import { safeFetchWrap } from '@atproto-labs/fetch-node'
21
- import { com } from './lexicons/index.js'
22
-
23
- export { AtUri, IdResolver }
24
- export type {
25
- AgentConfig,
26
- AtUriString,
27
- Cid,
28
- Commit,
29
- DidString,
30
- FetchHandler,
31
- LexMap,
32
- }
33
-
34
- /**
35
- * Resolve a record from the network.
36
- */
37
- export type RecordResolver = (
38
- uri: AtUri | AtUriString,
39
- ) => Promise<RecordResolution>
40
-
41
- /**
42
- * Resolve a record from the network, verifying its authenticity.
43
- */
44
- export type AtprotoRecordResolver = (
45
- uri: AtUri | AtUriString,
46
- options?: ResolveRecordOptions,
47
- ) => Promise<RecordResolution>
48
-
49
- export type BuildRecordResolverOptions = {
50
- idResolver?: IdResolver
51
- rpc?: Partial<AgentConfig> | FetchHandler
52
- }
53
-
54
- export type ResolveRecordOptions = {
55
- forceRefresh?: boolean
56
- }
57
-
58
- export type RecordResolution = {
59
- commit: Commit
60
- uri: AtUri
61
- cid: Cid
62
- record: LexMap
63
- }
64
-
65
- /**
66
- * Build a record resolver function.
67
- */
68
- export function buildRecordResolver(
69
- options: BuildRecordResolverOptions = {},
70
- ): AtprotoRecordResolver {
71
- const { idResolver = new IdResolver(), rpc } = options
72
- return async function resolveRecord(
73
- uriStr: AtUri | AtUriString,
74
- opts: ResolveRecordOptions = {},
75
- ): Promise<RecordResolution> {
76
- const uri = typeof uriStr === 'string' ? new AtUri(uriStr) : uriStr
77
- const did = await getDidFromUri(uri, { idResolver })
78
- const identityDoc = await idResolver.did
79
- .ensureResolve(did, opts.forceRefresh)
80
- .catch((err) => {
81
- throw new RecordResolutionError('Could not resolve DID identity data', {
82
- cause: err,
83
- })
84
- })
85
- const { pds, signingKey } = parseToAtprotoDocument(identityDoc)
86
- if (!pds) {
87
- throw new RecordResolutionError(
88
- 'Incomplete DID identity data: missing pds',
89
- )
90
- }
91
- if (!signingKey) {
92
- throw new RecordResolutionError(
93
- 'Incomplete DID identity data: missing signing key',
94
- )
95
- }
96
- const client = new Client(
97
- typeof rpc === 'function'
98
- ? { fetchHandler: rpc }
99
- : {
100
- ...rpc,
101
- service: rpc?.service ?? pds,
102
- fetch: rpc?.fetch ?? safeFetch,
103
- },
104
- )
105
- const proofBytes = await client
106
- .call(com.atproto.sync.getRecord, {
107
- did,
108
- collection: uri.collection as l.NsidString,
109
- rkey: uri.rkey as l.RecordKeyString,
110
- })
111
- .catch((err) => {
112
- throw new RecordResolutionError('Could not fetch record proof', {
113
- cause: err,
114
- })
115
- })
116
- const verified = await verifyRecordProof(proofBytes, {
117
- uri: AtUri.make(did, uri.collection, uri.rkey),
118
- signingKey,
119
- })
120
- return verified
121
- }
122
- }
123
-
124
- export const resolveRecord = buildRecordResolver()
125
-
126
- export const safeFetch = safeFetchWrap({
127
- allowIpHost: false,
128
- allowImplicitRedirect: true,
129
- responseMaxSize: (1024 + 10) * 1024, // 1MB + 10kB, just a bit larger than max record size
130
- })
131
-
132
- export class RecordResolutionError extends Error {
133
- constructor(message?: string, options?: ErrorOptions) {
134
- super(message, options)
135
- this.name = 'RecordResolutionError'
136
- }
137
- }
138
-
139
- async function getDidFromUri(
140
- uri: AtUri,
141
- { idResolver }: { idResolver: IdResolver },
142
- ): Promise<DidString> {
143
- if (l.isDidString(uri.host)) {
144
- return uri.host
145
- }
146
-
147
- const resolved = await idResolver.handle.resolve(uri.host)
148
- if (!resolved || !l.isDidString(resolved)) {
149
- throw new RecordResolutionError('Could not resolve handle found in AT-URI')
150
- }
151
-
152
- return resolved
153
- }
154
-
155
- async function verifyRecordProof(
156
- proofBytes: Uint8Array,
157
- { uri, signingKey }: { uri: AtUri; signingKey: string },
158
- ): Promise<RecordResolution> {
159
- const { root, blocks } = await readCarWithRoot(proofBytes).catch((err) => {
160
- throw new RecordResolutionError('Malformed record proof', { cause: err })
161
- })
162
- const blockstore = new MemoryBlockstore(blocks)
163
- const commit = await blockstore.readObj(root, repoDef.commit).catch((err) => {
164
- throw new RecordResolutionError('Invalid commit in record proof', {
165
- cause: err,
166
- })
167
- })
168
- if (commit.did !== uri.host) {
169
- throw new RecordResolutionError(`Invalid repo did: ${commit.did}`)
170
- }
171
- const validSig = await verifyCommitSig(commit, signingKey)
172
- if (!validSig) {
173
- throw new RecordResolutionError(
174
- `Invalid signature on commit: ${root.toString()}`,
175
- )
176
- }
177
- const mst = MST.load(blockstore, commit.data)
178
- const cid = await mst.get(`${uri.collection}/${uri.rkey}`)
179
- if (!cid) {
180
- throw new RecordResolutionError('Record not found in proof')
181
- }
182
- const record = (await blockstore.readRecord(cid)) as LexMap
183
- return { commit, uri, cid, record }
184
- }
package/src/util.ts DELETED
@@ -1,10 +0,0 @@
1
- import { ensureValidDid } from '@atproto/syntax'
2
-
3
- export function isValidDid(did: string) {
4
- try {
5
- ensureValidDid(did)
6
- return true
7
- } catch {
8
- return false
9
- }
10
- }
@@ -1,271 +0,0 @@
1
- import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
2
- import { SeedClient, TestNetworkNoAppView, usersSeed } from '@atproto/dev-env'
3
- import { DidString, NSID } from '@atproto/syntax'
4
- import {
5
- AtprotoLexiconResolver,
6
- buildLexiconResolver,
7
- resolveLexiconDidAuthority,
8
- } from '../src/index.js'
9
-
10
- const dnsEntries: [entry: string, ...result: string[][]][] = []
11
-
12
- vi.mock('node:dns/promises', () => {
13
- const mock = {
14
- resolveTxt: (entry: string) => {
15
- const found = dnsEntries.find(([e]) => e === entry)
16
- if (found) return found.slice(1)
17
- return []
18
- },
19
- }
20
- return { default: mock, ...mock }
21
- })
22
-
23
- describe('Lexicon resolution', () => {
24
- let network: TestNetworkNoAppView
25
- let sc: SeedClient
26
- let resolveLexicon: AtprotoLexiconResolver
27
-
28
- beforeAll(async () => {
29
- network = await TestNetworkNoAppView.create({
30
- dbPostgresSchema: 'lex_lexicon_resolution',
31
- })
32
- sc = network.getSeedClient()
33
- await usersSeed(sc)
34
- dnsEntries.push(['_lexicon.alice.example', [`did=${sc.dids.alice}`]])
35
- resolveLexicon = buildLexiconResolver({
36
- rpc: { fetch },
37
- idResolver: network.pds.ctx.idResolver,
38
- })
39
- }, 20_000) // @NOTE seeding can take a while
40
-
41
- afterAll(async () => {
42
- await network?.close()
43
- })
44
-
45
- it('resolves Lexicon.', async () => {
46
- const client = network.pds.getAgent()
47
- const lex = await client.com.atproto.lexicon.schema.create(
48
- { repo: sc.dids.alice, rkey: 'example.alice.name1' },
49
- { id: 'example.alice.name1', lexicon: 1, defs: {} },
50
- sc.getHeaders(sc.dids.alice),
51
- )
52
- const result = await resolveLexicon('example.alice.name1', {
53
- forceRefresh: true,
54
- })
55
- expect(result.commit.did).toEqual(sc.dids.alice)
56
- expect(result.cid.toString()).toEqual(lex.cid)
57
- expect(result.uri.toString()).toEqual(lex.uri)
58
- expect(result.nsid.toString()).toEqual('example.alice.name1')
59
- expect(result.lexicon).toEqual({
60
- $type: 'com.atproto.lexicon.schema',
61
- id: 'example.alice.name1',
62
- lexicon: 1,
63
- defs: {},
64
- })
65
- })
66
-
67
- it('fails on mismatched id.', async () => {
68
- const client = network.pds.getAgent()
69
- await client.com.atproto.lexicon.schema.create(
70
- { repo: sc.dids.alice, rkey: 'example.alice.mismatch' },
71
- { id: 'example.test1.mismatch.bad', lexicon: 1, defs: {} },
72
- sc.getHeaders(sc.dids.alice),
73
- )
74
- await expect(
75
- resolveLexicon('example.alice.mismatch', {
76
- forceRefresh: true,
77
- }),
78
- ).rejects.toThrow(
79
- 'Lexicon schema record id (example.test1.mismatch.bad) does not match NSID (example.alice.mismatch)',
80
- )
81
- })
82
-
83
- it('fails on missing DNS entry.', async () => {
84
- const client = network.pds.getAgent()
85
- await client.com.atproto.lexicon.schema.create(
86
- { repo: sc.dids.bob, rkey: 'example.bob.name' },
87
- { id: 'example.bob.name', lexicon: 1, defs: {} },
88
- sc.getHeaders(sc.dids.bob),
89
- )
90
- await expect(
91
- resolveLexicon('example.bob.name', {
92
- forceRefresh: true,
93
- }),
94
- ).rejects.toThrow(
95
- 'Could not resolve a DID authority for NSID (example.bob.name)',
96
- )
97
- })
98
-
99
- it('fails on missing record.', async () => {
100
- await expect(
101
- resolveLexicon('example.alice.missing', {
102
- forceRefresh: true,
103
- }),
104
- ).rejects.toThrow('Could not resolve Lexicon schema record')
105
- })
106
-
107
- it('fails on bad verification.', async () => {
108
- const client = network.pds.getAgent()
109
- const alicekey = await network.pds.ctx.actorStore.keypair(sc.dids.alice)
110
- const bobkey = await network.pds.ctx.actorStore.keypair(sc.dids.bob)
111
- await client.com.atproto.lexicon.schema.create(
112
- { repo: sc.dids.alice, rkey: 'example.alice.badsig' },
113
- { id: 'example.alice.badsig', lexicon: 1, defs: {} },
114
- sc.getHeaders(sc.dids.alice),
115
- )
116
- // switch alice's key away from the one used by her pds
117
- await network.pds.ctx.plcClient.updateAtprotoKey(
118
- sc.dids.alice,
119
- network.pds.ctx.plcRotationKey,
120
- bobkey.did(),
121
- )
122
- await expect(
123
- resolveLexicon('example.alice.badsig', {
124
- forceRefresh: true,
125
- }),
126
- ).rejects.toThrow(
127
- expect.objectContaining({
128
- name: 'LexiconResolutionError',
129
- message:
130
- 'Could not resolve Lexicon schema record (example.alice.badsig)',
131
- cause: expect.objectContaining({
132
- name: 'RecordResolutionError',
133
- message: expect.stringContaining('Invalid signature on commit'),
134
- }),
135
- }),
136
- )
137
- // reset alice's key
138
- await network.pds.ctx.plcClient.updateAtprotoKey(
139
- sc.dids.alice,
140
- network.pds.ctx.plcRotationKey,
141
- alicekey.did(),
142
- )
143
- })
144
-
145
- it('fails on invalid Lexicon document.', async () => {
146
- const client = network.pds.getAgent()
147
- await client.com.atproto.lexicon.schema.create(
148
- { repo: sc.dids.alice, rkey: 'example.alice.baddoc' },
149
- { id: 'example.alice.baddoc', lexicon: 999, defs: {} },
150
- sc.getHeaders(sc.dids.alice),
151
- )
152
- await expect(
153
- resolveLexicon('example.alice.baddoc', {
154
- forceRefresh: true,
155
- }),
156
- ).rejects.toThrow(
157
- expect.objectContaining({
158
- name: 'LexiconResolutionError',
159
- message: 'Invalid Lexicon document (example.alice.baddoc)',
160
- cause: expect.objectContaining({
161
- name: 'LexValidationError',
162
- }),
163
- }),
164
- )
165
- })
166
-
167
- it('resolves Lexicon based on override authority.', async () => {
168
- const client = network.pds.getAgent()
169
- await client.com.atproto.lexicon.schema.create(
170
- { repo: sc.dids.alice, rkey: 'example.alice.override' },
171
- {
172
- id: 'example.alice.override',
173
- lexicon: 1,
174
- defs: { alice: { type: 'string' } },
175
- },
176
- sc.getHeaders(sc.dids.alice),
177
- )
178
- const carolLex = await client.com.atproto.lexicon.schema.create(
179
- { repo: sc.dids.carol, rkey: 'example.alice.override' },
180
- {
181
- id: 'example.alice.override',
182
- lexicon: 1,
183
- defs: { carol: { type: 'string' } },
184
- },
185
- sc.getHeaders(sc.dids.carol),
186
- )
187
- const result = await resolveLexicon('example.alice.override', {
188
- didAuthority: sc.dids.carol as DidString,
189
- forceRefresh: true,
190
- })
191
- expect(result.commit.did).toEqual(sc.dids.carol)
192
- expect(result.cid.toString()).toEqual(carolLex.cid)
193
- expect(result.uri.toString()).toEqual(carolLex.uri)
194
- expect(result.nsid.toString()).toEqual('example.alice.override')
195
- expect(result.lexicon).toEqual({
196
- $type: 'com.atproto.lexicon.schema',
197
- id: 'example.alice.override',
198
- lexicon: 1,
199
- defs: { carol: { type: 'string' } },
200
- })
201
- })
202
-
203
- describe('DID authority', () => {
204
- it('handles a simple DNS resolution', async () => {
205
- dnsEntries.push(['_lexicon.simple.test', ['did=did:example:simpleDid']])
206
- const did = await resolveLexiconDidAuthority('test.simple.name')
207
- expect(did).toBe('did:example:simpleDid')
208
- })
209
-
210
- it('handles a noisy DNS resolution', async () => {
211
- dnsEntries.push([
212
- '_lexicon.noisy.test',
213
- ['blah blah blah'],
214
- ['did:example:fakeDid'],
215
- ['atproto=did:example:fakeDid'],
216
- ['did=did:example:noisyDid'],
217
- [
218
- 'chunk long domain aspdfoiuwerpoaisdfupasodfiuaspdfoiuasdpfoiausdfpaosidfuaspodifuaspdfoiuasdpfoiasudfpasodifuaspdofiuaspdfoiuasd',
219
- 'apsodfiuweproiasudfpoasidfu',
220
- ],
221
- ])
222
- const did = await resolveLexiconDidAuthority('test.noisy.name')
223
- expect(did).toBe('did:example:noisyDid')
224
- })
225
-
226
- it('handles a bad DNS resolution', async () => {
227
- dnsEntries.push([
228
- '_lexicon.bad.test',
229
- ['blah blah blah'],
230
- ['did:example:fakeDid'],
231
- ['atproto=did:example:fakeDid'],
232
- [
233
- 'chunk long domain aspdfoiuwerpoaisdfupasodfiuaspdfoiuasdpfoiausdfpaosidfuaspodifuaspdfoiuasdpfoiasudfpasodifuaspdofiuaspdfoiuasd',
234
- 'apsodfiuweproiasudfpoasidfu',
235
- ],
236
- ])
237
- const did = await resolveLexiconDidAuthority('test.bad.name')
238
- expect(did).toBeUndefined()
239
- })
240
-
241
- it('throws on multiple dids under same domain', async () => {
242
- dnsEntries.push([
243
- '_lexicon.bad.test',
244
- ['did=did:example:firstDid'],
245
- ['did=did:example:secondDid'],
246
- ])
247
- const did = await resolveLexiconDidAuthority('test.multi.name')
248
- expect(did).toBeUndefined()
249
- })
250
-
251
- it('fails on invalid NSID', async () => {
252
- // @ts-expect-error testing invalid input
253
- await expect(resolveLexiconDidAuthority('not an nsid')).rejects.toThrow(
254
- 'Disallowed characters in NSID',
255
- )
256
- })
257
-
258
- it('fails on invalid DID result', async () => {
259
- dnsEntries.push(['_lexicon.invalid.test', ['did=not:a:did']])
260
- const did = await resolveLexiconDidAuthority('test.invalid.name')
261
- expect(did).toBeUndefined()
262
- })
263
-
264
- it('accepts NSID object', async () => {
265
- const did = await resolveLexiconDidAuthority(
266
- NSID.parse('test.simple.name'),
267
- )
268
- expect(did).toBe('did:example:simpleDid')
269
- })
270
- })
271
- })
@@ -1,193 +0,0 @@
1
- import { afterAll, assert, beforeAll, describe, expect, it } from 'vitest'
2
- import { SeedClient, TestNetworkNoAppView, usersSeed } from '@atproto/dev-env'
3
- import { AtUriString, l } from '@atproto/lex'
4
- import { encode } from '@atproto/lex-cbor'
5
- import { AtprotoRecordResolver, buildRecordResolver } from '../src/index.js'
6
-
7
- describe('Record resolution', () => {
8
- let network: TestNetworkNoAppView
9
- let sc: SeedClient
10
- let resolveRecord: AtprotoRecordResolver
11
-
12
- beforeAll(async () => {
13
- network = await TestNetworkNoAppView.create({
14
- dbPostgresSchema: 'lex_record_resolution',
15
- })
16
- sc = network.getSeedClient()
17
- await usersSeed(sc)
18
- resolveRecord = buildRecordResolver({
19
- rpc: { fetch },
20
- idResolver: network.pds.ctx.idResolver,
21
- })
22
- }, 20_000) // @NOTE seeding can take a while
23
-
24
- afterAll(async () => {
25
- await network?.close()
26
- })
27
-
28
- it('resolves record by AT-URI object.', async () => {
29
- const post = await sc.post(sc.dids.alice, 'post1')
30
- const result = await resolveRecord(post.ref.uri, {
31
- forceRefresh: true,
32
- })
33
- expect(result.commit.did).toEqual(sc.dids.alice)
34
- expect(result.cid.toString()).toEqual(post.ref.cidStr)
35
- expect(result.uri.toString()).toEqual(post.ref.uriStr)
36
- expect(result.record.text).toEqual('post1')
37
- })
38
-
39
- it('resolves record by AT-URI string.', async () => {
40
- const post = await sc.post(sc.dids.alice, 'post2')
41
- assert(l.isAtUriString(post.ref.uriStr))
42
- const result = await resolveRecord(post.ref.uriStr, {
43
- forceRefresh: true,
44
- })
45
- expect(result.commit.did).toEqual(sc.dids.alice)
46
- expect(result.cid.toString()).toEqual(post.ref.cidStr)
47
- expect(result.uri.toString()).toEqual(post.ref.uriStr)
48
- expect(result.record.text).toEqual('post2')
49
- })
50
-
51
- it("does not resolve record that doesn't exist.", async () => {
52
- await expect(
53
- resolveRecord(`at://${sc.dids.alice}/app.bsky.feed.post/2222222222222`, {
54
- forceRefresh: true,
55
- }),
56
- ).rejects.toThrow('Record not found')
57
- })
58
-
59
- it('does not resolve record with bad commit signature.', async () => {
60
- const alicekey = await network.pds.ctx.actorStore.keypair(sc.dids.alice)
61
- const bobkey = await network.pds.ctx.actorStore.keypair(sc.dids.bob)
62
- const post = await sc.post(sc.dids.alice, 'post3')
63
- // switch alice's key away from the one used by her pds
64
- await network.pds.ctx.plcClient.updateAtprotoKey(
65
- sc.dids.alice,
66
- network.pds.ctx.plcRotationKey,
67
- bobkey.did(),
68
- )
69
- await expect(
70
- resolveRecord(post.ref.uri, {
71
- forceRefresh: true,
72
- }),
73
- ).rejects.toThrow('Invalid signature on commit')
74
- // reset alice's key
75
- await network.pds.ctx.plcClient.updateAtprotoKey(
76
- sc.dids.alice,
77
- network.pds.ctx.plcRotationKey,
78
- alicekey.did(),
79
- )
80
- })
81
-
82
- it('does not resolve record with corrupted CAR block.', async () => {
83
- const post = await sc.post(sc.dids.alice, 'post4')
84
- const badCbor = encode({})
85
- await network.pds.ctx.actorStore.transact(sc.dids.alice, (txn) =>
86
- txn.repo.db.db
87
- .updateTable('repo_block')
88
- .set({
89
- content: badCbor,
90
- size: badCbor.byteLength,
91
- })
92
- .where('cid', '=', post.ref.cidStr)
93
- .execute(),
94
- )
95
- await expect(
96
- resolveRecord(post.ref.uri, {
97
- forceRefresh: true,
98
- }),
99
- ).rejects.toThrow('Malformed record proof')
100
- })
101
-
102
- it('does not resolve record with missing signing key.', async () => {
103
- const post = await sc.post(sc.dids.alice, 'post5')
104
- await network.pds.ctx.plcClient.updateData(
105
- sc.dids.alice,
106
- network.pds.ctx.plcRotationKey,
107
- (doc) => {
108
- doc.verificationMethods = {
109
- not_atproto: doc.verificationMethods.atproto,
110
- }
111
- return doc
112
- },
113
- )
114
- await expect(
115
- resolveRecord(post.ref.uri, {
116
- forceRefresh: true,
117
- }),
118
- ).rejects.toThrow('Incomplete DID identity data: missing signing key')
119
- // reset alice's key
120
- await network.pds.ctx.plcClient.updateData(
121
- sc.dids.alice,
122
- network.pds.ctx.plcRotationKey,
123
- (doc) => {
124
- doc.verificationMethods = {
125
- atproto: doc.verificationMethods.not_atproto,
126
- }
127
- return doc
128
- },
129
- )
130
- })
131
-
132
- it('does not resolve record with missing pds.', async () => {
133
- const post = await sc.post(sc.dids.alice, 'post6')
134
- await network.pds.ctx.plcClient.updateData(
135
- sc.dids.alice,
136
- network.pds.ctx.plcRotationKey,
137
- (doc) => {
138
- doc.services = {
139
- not_atproto_pds: doc.services.atproto_pds,
140
- }
141
- return doc
142
- },
143
- )
144
- await expect(
145
- resolveRecord(post.ref.uri, {
146
- forceRefresh: true,
147
- }),
148
- ).rejects.toThrow('Incomplete DID identity data: missing pds')
149
- // reset alice's pds
150
- await network.pds.ctx.plcClient.updateData(
151
- sc.dids.alice,
152
- network.pds.ctx.plcRotationKey,
153
- (doc) => {
154
- doc.services = {
155
- atproto_pds: doc.services.not_atproto_pds,
156
- }
157
- return doc
158
- },
159
- )
160
- })
161
-
162
- it('resolves record despite missing at:// handle.', async () => {
163
- const post = await sc.post(sc.dids.alice, 'post7')
164
- await network.pds.ctx.plcClient.updateData(
165
- sc.dids.alice,
166
- network.pds.ctx.plcRotationKey,
167
- (doc) => {
168
- doc.alsoKnownAs = doc.alsoKnownAs.map((aka) =>
169
- aka.replace('at://', 'notat://'),
170
- )
171
- return doc
172
- },
173
- )
174
- const result = await resolveRecord(post.ref.uriStr as AtUriString, {
175
- forceRefresh: true,
176
- })
177
- expect(result.commit.did).toEqual(sc.dids.alice)
178
- expect(result.cid.toString()).toEqual(post.ref.cidStr)
179
- expect(result.uri.toString()).toEqual(post.ref.uriStr)
180
- expect(result.record.text).toEqual('post7')
181
- // reset alice's handle
182
- await network.pds.ctx.plcClient.updateData(
183
- sc.dids.alice,
184
- network.pds.ctx.plcRotationKey,
185
- (doc) => {
186
- doc.alsoKnownAs = doc.alsoKnownAs.map((aka) =>
187
- aka.replace('notat://', 'at://'),
188
- )
189
- return doc
190
- },
191
- )
192
- })
193
- })
@@ -1,9 +0,0 @@
1
- {
2
- "extends": ["../../tsconfig/node.json"],
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "noUnusedLocals": false,
7
- },
8
- "include": ["src"],
9
- }
@@ -1 +0,0 @@
1
- {"version":"7.0.0-dev.20260614.1","root":["./src/index.ts","./src/lexicon.ts","./src/record.ts","./src/util.ts","./src/lexicons/com.ts","./src/lexicons/index.ts","./src/lexicons/com/atproto.ts","./src/lexicons/com/atproto/lexicon.ts","./src/lexicons/com/atproto/sync.ts","./src/lexicons/com/atproto/lexicon/schema.defs.ts","./src/lexicons/com/atproto/lexicon/schema.ts","./src/lexicons/com/atproto/sync/getRecord.defs.ts","./src/lexicons/com/atproto/sync/getRecord.ts"]}
package/tsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "include": [],
3
- "references": [
4
- { "path": "./tsconfig.build.json" },
5
- { "path": "./tsconfig.tests.json" },
6
- ],
7
- }
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../tsconfig/vitest.json",
3
- "compilerOptions": {
4
- "rootDir": ".",
5
- "noUnusedLocals": false,
6
- },
7
- "include": ["./tests", "./src/**/*.test.ts"],
8
- }
package/vitest.config.ts DELETED
@@ -1,5 +0,0 @@
1
- import { defineProject } from 'vitest/config'
2
-
3
- export default defineProject({
4
- test: {},
5
- })