migrate the single-source feed api classes to the appview client

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-08-03 18:41:52 +03:00
parent 5e88752fc0
commit 234f34f4d5
6 changed files with 116 additions and 102 deletions
+24 -21
View File
@@ -1,23 +1,25 @@
import {
AppBskyFeedDefs,
type AppBskyFeedGetAuthorFeed as GetAuthorFeed,
type AtpAgent,
} from '@atproto/api'
import {AppBskyFeedDefs} from '@atproto/api'
import {type Client, type XrpcRequestParams} from '@atproto/lex'
import {app} from '#/lexicons'
import {type FeedAPI, type FeedAPIResponse} from './types'
type GetAuthorFeedParams = XrpcRequestParams<
typeof app.bsky.feed.getAuthorFeed.main
>
export class AuthorFeedAPI implements FeedAPI {
agent: AtpAgent
_params: GetAuthorFeed.QueryParams
client: Client
_params: GetAuthorFeedParams
constructor({
agent,
client,
feedParams,
}: {
agent: AtpAgent
feedParams: GetAuthorFeed.QueryParams
client: Client
feedParams: GetAuthorFeedParams
}) {
this.agent = agent
this.client = client
this._params = feedParams
}
@@ -28,11 +30,11 @@ export class AuthorFeedAPI implements FeedAPI {
}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.getAuthorFeed({
const data = await this.client.call(app.bsky.feed.getAuthorFeed, {
...this.params,
limit: 1,
})
return res.data.feed[0]
return data.feed[0]
}
async fetch({
@@ -42,19 +44,20 @@ export class AuthorFeedAPI implements FeedAPI {
cursor: string | undefined
limit: number
}): Promise<FeedAPIResponse> {
const res = await this.agent.getAuthorFeed({
/*
* A failed request rejects rather than resolving, so the error propagates
* to the query and drives the feed error UI (blocked actor, rate limit).
* The agent behaved the same way - its `success` flag was only ever true -
* so the empty-page branch this replaces was unreachable.
*/
const data = await this.client.call(app.bsky.feed.getAuthorFeed, {
...this.params,
cursor,
limit,
})
if (res.success) {
return {
cursor: res.data.cursor,
feed: this._filter(res.data.feed),
}
}
return {
feed: [],
cursor: data.cursor,
feed: this._filter(data.feed),
}
}
+5 -4
View File
@@ -1,13 +1,14 @@
import {type AppBskyFeedDefs, type AtpAgent} from '@atproto/api'
import {type AppBskyFeedDefs} from '@atproto/api'
import {type Client} from '@atproto/lex'
import {DEMO_FEED} from '#/lib/demo'
import {type FeedAPI, type FeedAPIResponse} from './types'
export class DemoFeedAPI implements FeedAPI {
agent: AtpAgent
client: Client
constructor({agent}: {agent: AtpAgent}) {
this.agent = agent
constructor({client}: {client: Client}) {
this.client = client
}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
+17 -14
View File
@@ -1,19 +1,21 @@
import {type AppBskyFeedDefs, type AtpAgent} from '@atproto/api'
import {type AppBskyFeedDefs} from '@atproto/api'
import {type Client} from '@atproto/lex'
import {app} from '#/lexicons'
import {type FeedAPI, type FeedAPIResponse} from './types'
export class FollowingFeedAPI implements FeedAPI {
agent: AtpAgent
client: Client
constructor({agent}: {agent: AtpAgent}) {
this.agent = agent
constructor({client}: {client: Client}) {
this.client = client
}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.getTimeline({
const data = await this.client.call(app.bsky.feed.getTimeline, {
limit: 1,
})
return res.data.feed[0]
return data.feed[0]
}
async fetch({
@@ -23,18 +25,19 @@ export class FollowingFeedAPI implements FeedAPI {
cursor: string | undefined
limit: number
}): Promise<FeedAPIResponse> {
const res = await this.agent.getTimeline({
/*
* A failed request rejects rather than resolving, so the error propagates
* to the query and drives the feed error UI. The agent behaved the same
* way - its `success` flag was only ever true - so the empty-page branch
* this replaces was unreachable.
*/
const data = await this.client.call(app.bsky.feed.getTimeline, {
cursor,
limit,
})
if (res.success) {
return {
cursor: res.data.cursor,
feed: res.data.feed,
}
}
return {
feed: [],
cursor: data.cursor,
feed: data.feed,
}
}
}
+26 -23
View File
@@ -1,32 +1,34 @@
import {
type AppBskyFeedDefs,
type AppBskyFeedGetActorLikes as GetActorLikes,
type AtpAgent,
} from '@atproto/api'
import {type AppBskyFeedDefs} from '@atproto/api'
import {type Client, type XrpcRequestParams} from '@atproto/lex'
import {app} from '#/lexicons'
import {type FeedAPI, type FeedAPIResponse} from './types'
type GetActorLikesParams = XrpcRequestParams<
typeof app.bsky.feed.getActorLikes.main
>
export class LikesFeedAPI implements FeedAPI {
agent: AtpAgent
params: GetActorLikes.QueryParams
client: Client
params: GetActorLikesParams
constructor({
agent,
client,
feedParams,
}: {
agent: AtpAgent
feedParams: GetActorLikes.QueryParams
client: Client
feedParams: GetActorLikesParams
}) {
this.agent = agent
this.client = client
this.params = feedParams
}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.getActorLikes({
const data = await this.client.call(app.bsky.feed.getActorLikes, {
...this.params,
limit: 1,
})
return res.data.feed[0]
return data.feed[0]
}
async fetch({
@@ -36,21 +38,22 @@ export class LikesFeedAPI implements FeedAPI {
cursor: string | undefined
limit: number
}): Promise<FeedAPIResponse> {
const res = await this.agent.getActorLikes({
/*
* A failed request rejects rather than resolving, so the error propagates
* to the query and drives the feed error UI. The agent behaved the same
* way - its `success` flag was only ever true - so the empty-page branch
* this replaces was unreachable.
*/
const data = await this.client.call(app.bsky.feed.getActorLikes, {
...this.params,
cursor,
limit,
})
if (res.success) {
// HACKFIX: the API incorrectly returns a cursor when there are no items -sfn
const isEmptyPage = res.data.feed.length === 0
return {
cursor: isEmptyPage ? undefined : res.data.cursor,
feed: res.data.feed,
}
}
// HACKFIX: the API incorrectly returns a cursor when there are no items -sfn
const isEmptyPage = data.feed.length === 0
return {
feed: [],
cursor: isEmptyPage ? undefined : data.cursor,
feed: data.feed,
}
}
}
+24 -21
View File
@@ -1,32 +1,34 @@
import {
type Agent,
type AppBskyFeedDefs,
type AppBskyFeedGetListFeed as GetListFeed,
} from '@atproto/api'
import {type AppBskyFeedDefs} from '@atproto/api'
import {type Client, type XrpcRequestParams} from '@atproto/lex'
import {app} from '#/lexicons'
import {type FeedAPI, type FeedAPIResponse} from './types'
type GetListFeedParams = XrpcRequestParams<
typeof app.bsky.feed.getListFeed.main
>
export class ListFeedAPI implements FeedAPI {
agent: Agent
params: GetListFeed.QueryParams
client: Client
params: GetListFeedParams
constructor({
agent,
client,
feedParams,
}: {
agent: Agent
feedParams: GetListFeed.QueryParams
client: Client
feedParams: GetListFeedParams
}) {
this.agent = agent
this.client = client
this.params = feedParams
}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.app.bsky.feed.getListFeed({
const data = await this.client.call(app.bsky.feed.getListFeed, {
...this.params,
limit: 1,
})
return res.data.feed[0]
return data.feed[0]
}
async fetch({
@@ -36,19 +38,20 @@ export class ListFeedAPI implements FeedAPI {
cursor: string | undefined
limit: number
}): Promise<FeedAPIResponse> {
const res = await this.agent.app.bsky.feed.getListFeed({
/*
* A failed request rejects rather than resolving, so the error propagates
* to the query and drives the feed error UI. The agent behaved the same
* way - its `success` flag was only ever true - so the empty-page branch
* this replaces was unreachable.
*/
const data = await this.client.call(app.bsky.feed.getListFeed, {
...this.params,
cursor,
limit,
})
if (res.success) {
return {
cursor: res.data.cursor,
feed: res.data.feed,
}
}
return {
feed: [],
cursor: data.cursor,
feed: data.feed,
}
}
}
+20 -19
View File
@@ -1,25 +1,25 @@
import {
type Agent,
type AppBskyFeedDefs,
type AppBskyFeedGetPosts,
} from '@atproto/api'
import {type AppBskyFeedDefs} from '@atproto/api'
import {type Client, type XrpcRequestParams} from '@atproto/lex'
import {logger} from '#/logger'
import {app} from '#/lexicons'
import {type FeedAPI, type FeedAPIResponse} from './types'
type GetPostsParams = XrpcRequestParams<typeof app.bsky.feed.getPosts.main>
export class PostListFeedAPI implements FeedAPI {
agent: Agent
params: AppBskyFeedGetPosts.QueryParams
client: Client
params: GetPostsParams
peek: AppBskyFeedDefs.FeedViewPost | null = null
constructor({
agent,
client,
feedParams,
}: {
agent: Agent
feedParams: AppBskyFeedGetPosts.QueryParams
client: Client
feedParams: GetPostsParams
}) {
this.agent = agent
this.client = client
if (feedParams.uris.length > 25) {
logger.warn(
`Too many URIs provided - expected 25, got ${feedParams.uris.length}`,
@@ -36,17 +36,18 @@ export class PostListFeedAPI implements FeedAPI {
}
async fetch({}: {}): Promise<FeedAPIResponse> {
const res = await this.agent.app.bsky.feed.getPosts({
/*
* A failed request rejects rather than resolving, so the error propagates
* to the query and drives the feed error UI. The agent behaved the same
* way - its `success` flag was only ever true - so the empty-page branch
* this replaces was unreachable.
*/
const data = await this.client.call(app.bsky.feed.getPosts, {
...this.params,
})
if (res.success) {
this.peek = {post: res.data.posts[0]}
return {
feed: res.data.posts.map(post => ({post})),
}
}
this.peek = {post: data.posts[0]}
return {
feed: [],
feed: data.posts.map(post => ({post})),
}
}
}