migrate runtime call sites to lex clients and sdk actions

Phase 3 tasks 3-7 (parallel wave): composer/post pipeline on
pdsClient/appviewClient with structural+instance blob guards and a golden
CID fixture test; chat Convo/EventBus/queries on the dedicated chat client;
preferences sugar to SDK actions on the PDS client; remaining state/queries
producers (usePostThread unspecced flip, video scoped-token clients,
notifications, starter packs, lists) to client.call; UI runtime sweep
(AtUri from @atproto/syntax, moderation fns from @bsky.app/sdk/moderation,
SDK RichText, ozone reason tokens, guard rewrites via #/types/bsky).

Intermediate checkpoint (hooks skipped): ~114 typecheck errors remain in
cross-boundary consumer files, resolved by the type-only codemod next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-07-16 21:04:44 +03:00
parent b1a4e1cd16
commit 300a50b69a
296 changed files with 4507 additions and 3903 deletions
+87 -81
View File
@@ -1,14 +1,19 @@
import {useCallback} from 'react'
import {type Un$Typed} from '@atproto/lex'
import {
type AppBskyActorDefs,
type AppBskyActorGetProfile,
type AppBskyActorGetProfiles,
type AppBskyActorProfile,
type AppBskyGraphGetFollows,
AtUri,
type ComAtprotoRepoUploadBlob,
type Un$Typed,
} from '@atproto/api'
type AtIdentifierString,
type Client,
type DidString,
} from '@atproto/lex-client'
import {type DatetimeString} from '@atproto/lex-schema'
import {AtUri, type AtUriString} from '@atproto/syntax'
import {
deleteFollow,
follow,
muteActor,
unmuteActor,
upsertProfile,
} from '@bsky.app/sdk'
import {
type InfiniteData,
keepPreviousData,
@@ -32,10 +37,11 @@ import {
useUnstableProfileViewCache,
} from '#/state/queries/unstable-profile-cache'
import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache'
import {type SessionAgent, useAgent, useSession} from '#/state/session'
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
import * as userActionHistory from '#/state/userActionHistory'
import {useAnalytics} from '#/analytics'
import {type Metrics, toClout} from '#/analytics/metrics'
import {app} from '#/lexicons'
import type * as bsky from '#/types/bsky'
import {
ProgressGuideAction,
@@ -67,9 +73,9 @@ export function useProfileQuery({
did: string | undefined
staleTime?: number
}) {
const agent = useAgent()
const client = useAppviewClient()
const {getUnstableProfile} = useUnstableProfileViewCache()
return useQuery<AppBskyActorDefs.ProfileViewDetailed>({
return useQuery<app.bsky.actor.defs.ProfileViewDetailed>({
// WARNING
// this staleTime is load-bearing
// if you remove it, the UI infinite-loops
@@ -78,12 +84,13 @@ export function useProfileQuery({
refetchOnWindowFocus: true,
queryKey: RQKEY(did ?? ''),
queryFn: async () => {
const res = await agent.getProfile({actor: did ?? ''})
return res.data
return await client.call(app.bsky.actor.getProfile, {
actor: (did ?? '') as AtIdentifierString,
})
},
placeholderData: () => {
if (!did) return
return getUnstableProfile(did) as AppBskyActorDefs.ProfileViewDetailed
return getUnstableProfile(did) as app.bsky.actor.defs.ProfileViewDetailed
},
enabled: !!did,
})
@@ -96,21 +103,22 @@ export function useProfilesQuery({
handles: string[]
maintainData?: boolean
}) {
const agent = useAgent()
const client = useAppviewClient()
return useQuery({
enabled: handles.length > 0,
staleTime: STALE.MINUTES.FIVE,
queryKey: profilesQueryKey(handles),
queryFn: async () => {
const res = await agent.getProfiles({actors: handles})
return res.data
return await client.call(app.bsky.actor.getProfiles, {
actors: handles as AtIdentifierString[],
})
},
placeholderData: maintainData ? keepPreviousData : undefined,
})
}
export function usePrefetchProfileQuery() {
const agent = useAgent()
const client = useAppviewClient()
const queryClient = useQueryClient()
const prefetchProfileQuery = useCallback(
async (did: string) => {
@@ -118,30 +126,32 @@ export function usePrefetchProfileQuery() {
staleTime: STALE.SECONDS.THIRTY,
queryKey: RQKEY(did),
queryFn: async () => {
const res = await agent.getProfile({actor: did || ''})
return res.data
return await client.call(app.bsky.actor.getProfile, {
actor: (did || '') as AtIdentifierString,
})
},
})
},
[queryClient, agent],
[queryClient, client],
)
return prefetchProfileQuery
}
interface ProfileUpdateParams {
profile: AppBskyActorDefs.ProfileViewDetailed
profile: app.bsky.actor.defs.ProfileViewDetailed
updates:
| Un$Typed<AppBskyActorProfile.Record>
| Un$Typed<app.bsky.actor.profile.Main>
| ((
existing: Un$Typed<AppBskyActorProfile.Record>,
) => Un$Typed<AppBskyActorProfile.Record>)
existing: Un$Typed<app.bsky.actor.profile.Main>,
) => Un$Typed<app.bsky.actor.profile.Main>)
newUserAvatar?: ImageMeta | undefined | null
newUserBanner?: ImageMeta | undefined | null
checkCommitted?: (res: AppBskyActorGetProfile.Response) => boolean
checkCommitted?: (res: app.bsky.actor.getProfile.$OutputBody) => boolean
}
export function useProfileUpdateMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
const pdsClient = usePdsClient()
const appviewClient = useAppviewClient()
const updateProfileVerificationCache = useUpdateProfileVerificationCache()
return useMutation<void, Error, ProfileUpdateParams>({
mutationFn: async ({
@@ -151,28 +161,24 @@ export function useProfileUpdateMutation() {
newUserBanner,
checkCommitted,
}) => {
let newUserAvatarPromise:
| Promise<ComAtprotoRepoUploadBlob.Response>
| undefined
let newUserAvatarPromise: ReturnType<typeof uploadBlob> | undefined
if (newUserAvatar) {
newUserAvatarPromise = uploadBlob(
agent,
pdsClient,
newUserAvatar.path,
newUserAvatar.mime,
)
}
let newUserBannerPromise:
| Promise<ComAtprotoRepoUploadBlob.Response>
| undefined
let newUserBannerPromise: ReturnType<typeof uploadBlob> | undefined
if (newUserBanner) {
newUserBannerPromise = uploadBlob(
agent,
pdsClient,
newUserBanner.path,
newUserBanner.mime,
)
}
await agent.upsertProfile(async existing => {
let next: Un$Typed<AppBskyActorProfile.Record> = existing || {}
await pdsClient.call(upsertProfile, async existing => {
let next: Un$Typed<app.bsky.actor.profile.Main> = existing || {}
if (typeof updates === 'function') {
next = updates(next)
} else {
@@ -184,37 +190,37 @@ export function useProfileUpdateMutation() {
}
if (newUserAvatarPromise) {
const res = await newUserAvatarPromise
next.avatar = res.data.blob
next.avatar = res.blob
} else if (newUserAvatar === null) {
next.avatar = undefined
}
if (newUserBannerPromise) {
const res = await newUserBannerPromise
next.banner = res.data.blob
next.banner = res.blob
} else if (newUserBanner === null) {
next.banner = undefined
}
return next
})
await whenAppViewReady(
agent,
appviewClient,
profile.did,
checkCommitted ||
(res => {
if (typeof newUserAvatar !== 'undefined') {
if (newUserAvatar === null && res.data.avatar) {
if (newUserAvatar === null && res.avatar) {
// url hasn't cleared yet
return false
} else if (res.data.avatar === profile.avatar) {
} else if (res.avatar === profile.avatar) {
// url hasn't changed yet
return false
}
}
if (typeof newUserBanner !== 'undefined') {
if (newUserBanner === null && res.data.banner) {
if (newUserBanner === null && res.banner) {
// url hasn't cleared yet
return false
} else if (res.data.banner === profile.banner) {
} else if (res.banner === profile.banner) {
// url hasn't changed yet
return false
}
@@ -223,8 +229,8 @@ export function useProfileUpdateMutation() {
return true
}
return (
res.data.displayName === updates.displayName &&
res.data.description === updates.description
res.displayName === updates.displayName &&
res.description === updates.description
)
}),
)
@@ -248,7 +254,7 @@ export function useProfileFollowMutationQueue(
position?: number,
contextProfileDid?: string,
) {
const agent = useAgent()
const client = useAppviewClient()
const queryClient = useQueryClient()
const {currentAccount} = useSession()
const did = profile.did
@@ -290,7 +296,7 @@ export function useProfileFollowMutationQueue(
// Optimistically update profile follows cache for avatar displays
if (currentAccount?.did) {
type FollowsQueryData =
InfiniteData<AppBskyGraphGetFollows.OutputSchema>
InfiniteData<app.bsky.graph.getFollows.$OutputBody>
queryClient.setQueryData<FollowsQueryData>(
PROFILE_FOLLOWS_RQKEY(currentAccount.did),
old => {
@@ -307,7 +313,7 @@ export function useProfileFollowMutationQueue(
{
...old.pages[0],
follows: [
profile as AppBskyActorDefs.ProfileView,
profile as app.bsky.actor.defs.ProfileView,
...old.pages[0].follows,
],
},
@@ -329,12 +335,12 @@ export function useProfileFollowMutationQueue(
}
if (finalFollowingUri) {
void agent.app.bsky.graph
.getSuggestedFollowsByActor({
actor: did,
void client
.call(app.bsky.graph.getSuggestedFollowsByActor, {
actor: did as AtIdentifierString,
})
.then(res => {
const dids = res.data.suggestions
const dids = res.suggestions
.filter(a => !a.viewer?.following)
.map(a => a.did)
.slice(0, 8)
@@ -371,13 +377,13 @@ function useProfileFollowMutation(
) {
const ax = useAnalytics()
const {currentAccount} = useSession()
const agent = useAgent()
const pdsClient = usePdsClient()
const queryClient = useQueryClient()
const {captureAction} = useProgressGuideControls()
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
mutationFn: async ({did}) => {
let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined
let ownProfile: app.bsky.actor.defs.ProfileViewDetailed | undefined
if (currentAccount) {
ownProfile = findProfileQueryData(queryClient, currentAccount.did)
}
@@ -396,7 +402,7 @@ function useProfileFollowMutation(
position,
contextProfileDid,
})
return await agent.follow(did)
return await pdsClient.call(follow, {did: did as DidString})
},
})
}
@@ -405,11 +411,11 @@ function useProfileUnfollowMutation(
logContext: Metrics['profile:unfollow']['logContext'],
) {
const ax = useAnalytics()
const agent = useAgent()
const pdsClient = usePdsClient()
return useMutation<void, Error, {did: string; followUri: string}>({
mutationFn: async ({followUri}) => {
ax.metric('profile:unfollow', {logContext})
return await agent.deleteFollow(followUri)
return await pdsClient.call(deleteFollow, followUri as AtUriString)
},
})
}
@@ -468,10 +474,10 @@ export function useProfileMuteMutationQueue(
function useProfileMuteMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
const pdsClient = usePdsClient()
return useMutation<void, Error, {did: string}>({
mutationFn: async ({did}) => {
await agent.mute(did)
await pdsClient.call(muteActor, {actor: did as AtIdentifierString})
},
onSuccess() {
void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
@@ -481,10 +487,10 @@ function useProfileMuteMutation() {
function useProfileUnmuteMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
const pdsClient = usePdsClient()
return useMutation<void, Error, {did: string}>({
mutationFn: async ({did}) => {
await agent.unmute(did)
await pdsClient.call(unmuteActor, {actor: did as AtIdentifierString})
},
onSuccess() {
void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
@@ -556,17 +562,17 @@ export function useProfileBlockMutationQueue(
function useProfileBlockMutation() {
const {currentAccount} = useSession()
const agent = useAgent()
const pdsClient = usePdsClient()
const queryClient = useQueryClient()
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
mutationFn: async ({did}) => {
if (!currentAccount) {
throw new Error('Not signed in')
}
return await agent.app.bsky.graph.block.create(
{repo: currentAccount.did},
{subject: did, createdAt: new Date().toISOString()},
)
return await pdsClient.create(app.bsky.graph.block, {
subject: did as DidString,
createdAt: new Date().toISOString() as DatetimeString,
})
},
onSuccess(_, {did}) {
void queryClient.invalidateQueries({queryKey: RQKEY_MY_BLOCKED()})
@@ -577,7 +583,7 @@ function useProfileBlockMutation() {
function useProfileUnblockMutation() {
const {currentAccount} = useSession()
const agent = useAgent()
const pdsClient = usePdsClient()
const queryClient = useQueryClient()
return useMutation<void, Error, {did: string; blockUri: string}>({
mutationFn: async ({blockUri}) => {
@@ -585,10 +591,7 @@ function useProfileUnblockMutation() {
throw new Error('Not signed in')
}
const {rkey} = new AtUri(blockUri)
await agent.app.bsky.graph.block.delete({
repo: currentAccount.did,
rkey,
})
await pdsClient.delete(app.bsky.graph.block, {rkey})
},
onSuccess(_, {did}) {
resetProfilePostsQueries(queryClient, did, 1000)
@@ -597,24 +600,27 @@ function useProfileUnblockMutation() {
}
async function whenAppViewReady(
agent: SessionAgent,
client: Client,
actor: string,
fn: (res: AppBskyActorGetProfile.Response) => boolean,
fn: (res: app.bsky.actor.getProfile.$OutputBody) => boolean,
) {
await until(
5, // 5 tries
1e3, // 1s delay between tries
fn,
() => agent.app.bsky.actor.getProfile({actor}),
() =>
client.call(app.bsky.actor.getProfile, {
actor: actor as AtIdentifierString,
}),
)
}
export function* findAllProfilesInQueryData(
queryClient: QueryClient,
did: string,
): Generator<AppBskyActorDefs.ProfileViewDetailed, void> {
): Generator<app.bsky.actor.defs.ProfileViewDetailed, void> {
const profileQueryDatas =
queryClient.getQueriesData<AppBskyActorDefs.ProfileViewDetailed>({
queryClient.getQueriesData<app.bsky.actor.defs.ProfileViewDetailed>({
queryKey: [RQKEY_ROOT],
})
for (const [_queryKey, queryData] of profileQueryDatas) {
@@ -626,7 +632,7 @@ export function* findAllProfilesInQueryData(
}
}
const profilesQueryDatas =
queryClient.getQueriesData<AppBskyActorGetProfiles.OutputSchema>({
queryClient.getQueriesData<app.bsky.actor.getProfiles.$OutputBody>({
queryKey: [profilesQueryKeyRoot],
})
for (const [_queryKey, queryData] of profilesQueryDatas) {
@@ -644,8 +650,8 @@ export function* findAllProfilesInQueryData(
export function findProfileQueryData(
queryClient: QueryClient,
did: string,
): AppBskyActorDefs.ProfileViewDetailed | undefined {
return queryClient.getQueryData<AppBskyActorDefs.ProfileViewDetailed>(
): app.bsky.actor.defs.ProfileViewDetailed | undefined {
return queryClient.getQueryData<app.bsky.actor.defs.ProfileViewDetailed>(
RQKEY(did),
)
}