Skip to content

Commit 790f6c8

Browse files
committed
refactor: batch pds-users.get.ts requests
1 parent 24196d6 commit 790f6c8

File tree

4 files changed

+56
-37
lines changed

4 files changed

+56
-37
lines changed

app/pages/pds.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import type { AtprotoProfile } from '#server/api/atproto/users.get.ts'
2+
import type { AtprotoProfile } from '#server/api/atproto/pds-users.get.ts'
33
44
const router = useRouter()
55
const canGoBack = useCanGoBack()

server/api/atproto/pds-users.get.ts

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,53 @@
1-
export interface AtprotoProfile {
2-
did: string
3-
handle: string
4-
displayName?: string
5-
avatar?: string
6-
}
1+
import {
2+
ONE_THOUSAND_NPMX_USER_ACCOUNTS_XRPC,
3+
BSKY_APP_VIEW_USER_PROFILES_XRPC,
4+
ERROR_PDS_FETCH_FAILED,
5+
} from '#shared/utils/constants'
6+
import type { AtprotoProfile } from '#shared/types/atproto'
7+
8+
const USER_BATCH_AMOUNT = 25
79

810
export default defineCachedEventHandler(
911
async (): Promise<AtprotoProfile[]> => {
10-
const response = await fetch('https://npmx.social/xrpc/com.atproto.sync.listRepos?limit=1000')
12+
// INFO: Request npmx.social PDS for every hosted user account
13+
const response = await fetch(ONE_THOUSAND_NPMX_USER_ACCOUNTS_XRPC)
1114

1215
if (!response.ok) {
1316
throw createError({
1417
statusCode: response.status,
15-
message: 'Failed to fetch PDS repos',
18+
message: ERROR_PDS_FETCH_FAILED,
1619
})
1720
}
1821

1922
const listRepos = (await response.json()) as { repos: { did: string }[] }
2023
const dids = listRepos.repos.map(repo => repo.did)
2124

22-
const getProfilesUrl = 'https://public.api.bsky.app/xrpc/app.bsky.actor.getProfiles'
23-
const allProfiles: AtprotoProfile[] = []
24-
25-
for (let i = 0; i < dids.length; i += 25) {
26-
const batch = dids.slice(i, i + 25)
27-
28-
const params = new URLSearchParams()
29-
for (const did of batch) {
30-
params.append('actors', did)
31-
}
32-
33-
try {
34-
const profilesResponse = await fetch(`${getProfilesUrl}?${params.toString()}`)
35-
36-
if (!profilesResponse.ok) {
37-
console.warn(`Failed to fetch atproto profiles: ${profilesResponse.status}`)
38-
continue
39-
}
40-
41-
const profilesData = (await profilesResponse.json()) as { profiles: AtprotoProfile[] }
42-
43-
if (profilesData.profiles) {
44-
allProfiles.push(...profilesData.profiles)
45-
}
46-
} catch (error) {
47-
console.warn('Failed to fetch atproto profiles:', error)
48-
}
25+
// INFO: Request the list of user profiles from the Bluesky AppView
26+
const batchPromises: Promise<AtprotoProfile[]>[] = []
27+
28+
for (let i = 0; i < dids.length; i += USER_BATCH_AMOUNT) {
29+
const batch = dids.slice(i, i + USER_BATCH_AMOUNT)
30+
const url = new URL(BSKY_APP_VIEW_USER_PROFILES_XRPC)
31+
32+
for (const did of batch) url.searchParams.append('actors', did)
33+
34+
batchPromises.push(
35+
fetch(url.toString())
36+
.then(res => {
37+
if (!res.ok) throw new Error(`Status ${res.status}`)
38+
return res.json() as Promise<{ profiles: AtprotoProfile[] }>
39+
})
40+
.then(data => data.profiles || [])
41+
.catch(err => {
42+
console.warn('Failed to fetch batch:', err)
43+
// Return empty array on failure so Promise.all doesn't crash
44+
return []
45+
}),
46+
)
4947
}
5048

51-
return allProfiles
49+
// INFO: Await all batches in parallel and flatten the results
50+
return (await Promise.all(batchPromises)).flat()
5251
},
5352
{
5453
maxAge: 3600,

shared/types/atproto.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* The lightweight view of a public profile for an AT Protocol user
3+
*/
4+
export type AtprotoProfile = {
5+
// The unique Decentralized Identifier (DID)
6+
did: string
7+
// User handle (e.g. user.bsky.social or user.com)
8+
handle: string
9+
// Display name, if present
10+
displayName?: string
11+
// URL of the avatar image, if present
12+
avatar?: string
13+
}

shared/utils/constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ export const BLUESKY_API = 'https://public.api.bsky.app'
1414
export const BLUESKY_COMMENTS_REQUEST = '/api/atproto/bluesky-comments'
1515
export const NPM_REGISTRY = 'https://registry.npmjs.org'
1616
export const NPM_API = 'https://api.npmjs.org'
17+
export const ONE_THOUSAND_NPMX_USER_ACCOUNTS_XRPC =
18+
'https://npmx.social/xrpc/com.atproto.sync.listRepos?limit=1000'
19+
export const BSKY_APP_VIEW_USER_PROFILES_XRPC =
20+
'https://public.api.bsky.app/xrpc/app.bsky.actor.getProfiles'
21+
22+
// Error Messages
1723
export const ERROR_PACKAGE_ANALYSIS_FAILED = 'Failed to analyze package.'
1824
export const ERROR_PACKAGE_VERSION_AND_FILE_FAILED = 'Version and file path are required.'
1925
export const ERROR_PACKAGE_REQUIREMENTS_FAILED =
@@ -25,6 +31,7 @@ export const NPM_MISSING_README_SENTINEL = 'ERROR: No README data found!'
2531
export const NPM_README_TRUNCATION_THRESHOLD = 64_000
2632
export const ERROR_JSR_FETCH_FAILED = 'Failed to fetch package from JSR registry.'
2733
export const ERROR_NPM_FETCH_FAILED = 'Failed to fetch package from npm registry.'
34+
export const ERROR_PDS_FETCH_FAILED = 'Failed to fetch PDS repos.'
2835
export const ERROR_PROVENANCE_FETCH_FAILED = 'Failed to fetch provenance.'
2936
export const UNSET_NUXT_SESSION_PASSWORD = 'NUXT_SESSION_PASSWORD not set'
3037
export const ERROR_SUGGESTIONS_FETCH_FAILED = 'Failed to fetch suggestions.'

0 commit comments

Comments
 (0)