forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import { CONSTELLATION_HOST } from '#shared/utils/constants'
2import type { CachedFetchFunction } from './fetch-cache-config'
3
4export type Backlink = {
5 did: string
6 collection: string
7 rkey: string
8}
9
10export type BacklinksResponse = {
11 total: number
12 records: Backlink[]
13 cursor: string | undefined
14}
15
16export type LinksDistinctDidsResponse = {
17 total: number
18 linking_dids: string[]
19 cursor: string | undefined
20}
21
22export type AllLinksResponse = {
23 links: Record<
24 string,
25 Record<
26 string,
27 {
28 records: number
29 distinct_dids: number
30 }
31 >
32 >
33}
34
35const HEADERS = { 'User-Agent': 'npmx' }
36
37export class Constellation {
38 private readonly cachedFetch: CachedFetchFunction
39 constructor(fetch: CachedFetchFunction) {
40 this.cachedFetch = fetch
41 }
42
43 /**
44 * Gets backlinks from constellation
45 * https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks?subject=at%3A%2F%2Fdid%3Aplc%3Aa4pqq234yw7fqbddawjo7y35%2Fapp.bsky.feed.post%2F3m237ilwc372e&source=app.bsky.feed.like%3Asubject.uri&limit=16
46 * @param subject - A uri encoded link. did, url, or at-uri
47 * @param collection - The lexicon collection to check like dev.npmx.feed.like
48 * @param recordPath - Where in the record to check for the subject
49 * @param limit - The number of backlinks to return
50 * @param cursor - The cursor to use for pagination
51 * @param reverse - Whether to reverse the order of the results
52 * @param filterByDids - An array of dids to filter by in the results
53 * @param ttl - The ttl to use for the cache
54 */
55 async getBackLinks(
56 subject: string,
57 collection: string,
58 recordPath: string,
59 limit = 16,
60 cursor?: string,
61 reverse = false,
62 filterByDids: [string][] = [],
63 ttl: number | undefined = undefined,
64 ) {
65 // Note that using Client from @atproto/lex here is kinda "hard" because it
66 // expects a native fetch implementation, which is "hard" to provide using
67 // this.cachedFetch as underlying fetch implementation. In addition to this,
68 // blue.microcosm.links.getBacklinks is not a published lexicon, meaning
69 // that we cannot install it using `pnpm exec lex install
70 // blue.microcosm.links.getBacklinks` and get generated type definitions,
71 // which kinda defeats the purpose of using Client in the first place. For
72 // these reasons, we are using this.cachedFetch directly to call the
73 // constellation API endpoint, and type casting the response.
74 const source = encodeURIComponent(`${collection}:${recordPath}`)
75 let urlToCall = `https://${CONSTELLATION_HOST}/xrpc/blue.microcosm.links.getBacklinks?subject=${encodeURIComponent(subject)}&source=${source}&limit=${limit}`
76 if (cursor) urlToCall += `&cursor=${cursor}`
77 if (reverse) urlToCall += '&reverse=true'
78 filterByDids.forEach(did => (urlToCall += `&did=${did}`))
79
80 return await this.cachedFetch<BacklinksResponse>(urlToCall, { headers: HEADERS }, ttl)
81 }
82
83 /**
84 * Gets the distinct dids that link to a target record
85 * @param target - A uri encoded link. did, url, or at-uri
86 * @param collection - The lexicon collection to check like dev.npmx.feed.like
87 * @param recordPath - Where in the record to check for the subject
88 * @param limit - The number of distinct dids to return
89 * @param cursor - The cursor to use for pagination
90 * @param ttl - The ttl to use for the cache
91 */
92 async getLinksDistinctDids(
93 target: string,
94 collection: string,
95 recordPath: string,
96 limit: number = 16,
97 cursor?: string,
98 ttl: number | undefined = undefined,
99 ) {
100 let urlToCall = `https://${CONSTELLATION_HOST}/links/distinct-dids?target=${encodeURIComponent(target)}&collection=${collection}&path=${recordPath}&limit=${limit}`
101 if (cursor) urlToCall += `&cursor=${cursor}`
102 return await this.cachedFetch<LinksDistinctDidsResponse>(urlToCall, { headers: HEADERS }, ttl)
103 }
104
105 /**
106 * Gets all links from constellation and their counts
107 * @param target - A uri encoded link. did, url, or at-uri
108 * @param ttl - The ttl to use for the cache
109 */
110 async getAllLinks(target: string, ttl: number | undefined = undefined) {
111 return await this.cachedFetch<AllLinksResponse>(
112 `https://${CONSTELLATION_HOST}/links/all?target=${target}`,
113 { headers: HEADERS },
114 ttl,
115 )
116 }
117}