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 { import {AppBskyFeedDefs} from '@atproto/api'
AppBskyFeedDefs, import {type Client, type XrpcRequestParams} from '@atproto/lex'
type AppBskyFeedGetAuthorFeed as GetAuthorFeed,
type AtpAgent,
} from '@atproto/api'
import {app} from '#/lexicons'
import {type FeedAPI, type FeedAPIResponse} from './types' import {type FeedAPI, type FeedAPIResponse} from './types'
type GetAuthorFeedParams = XrpcRequestParams<
typeof app.bsky.feed.getAuthorFeed.main
>
export class AuthorFeedAPI implements FeedAPI { export class AuthorFeedAPI implements FeedAPI {
agent: AtpAgent client: Client
_params: GetAuthorFeed.QueryParams _params: GetAuthorFeedParams
constructor({ constructor({
agent, client,
feedParams, feedParams,
}: { }: {
agent: AtpAgent client: Client
feedParams: GetAuthorFeed.QueryParams feedParams: GetAuthorFeedParams
}) { }) {
this.agent = agent this.client = client
this._params = feedParams this._params = feedParams
} }
@@ -28,11 +30,11 @@ export class AuthorFeedAPI implements FeedAPI {
} }
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.getAuthorFeed({ const data = await this.client.call(app.bsky.feed.getAuthorFeed, {
...this.params, ...this.params,
limit: 1, limit: 1,
}) })
return res.data.feed[0] return data.feed[0]
} }
async fetch({ async fetch({
@@ -42,19 +44,20 @@ export class AuthorFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): 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, ...this.params,
cursor, cursor,
limit, limit,
}) })
if (res.success) {
return { return {
cursor: res.data.cursor, cursor: data.cursor,
feed: this._filter(res.data.feed), feed: this._filter(data.feed),
}
}
return {
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 {DEMO_FEED} from '#/lib/demo'
import {type FeedAPI, type FeedAPIResponse} from './types' import {type FeedAPI, type FeedAPIResponse} from './types'
export class DemoFeedAPI implements FeedAPI { export class DemoFeedAPI implements FeedAPI {
agent: AtpAgent client: Client
constructor({agent}: {agent: AtpAgent}) { constructor({client}: {client: Client}) {
this.agent = agent this.client = client
} }
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { 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' import {type FeedAPI, type FeedAPIResponse} from './types'
export class FollowingFeedAPI implements FeedAPI { export class FollowingFeedAPI implements FeedAPI {
agent: AtpAgent client: Client
constructor({agent}: {agent: AtpAgent}) { constructor({client}: {client: Client}) {
this.agent = agent this.client = client
} }
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.getTimeline({ const data = await this.client.call(app.bsky.feed.getTimeline, {
limit: 1, limit: 1,
}) })
return res.data.feed[0] return data.feed[0]
} }
async fetch({ async fetch({
@@ -23,18 +25,19 @@ export class FollowingFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): 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, cursor,
limit, limit,
}) })
if (res.success) {
return { return {
cursor: res.data.cursor, cursor: data.cursor,
feed: res.data.feed, feed: data.feed,
}
}
return {
feed: [],
} }
} }
} }
+25 -22
View File
@@ -1,32 +1,34 @@
import { import {type AppBskyFeedDefs} from '@atproto/api'
type AppBskyFeedDefs, import {type Client, type XrpcRequestParams} from '@atproto/lex'
type AppBskyFeedGetActorLikes as GetActorLikes,
type AtpAgent,
} from '@atproto/api'
import {app} from '#/lexicons'
import {type FeedAPI, type FeedAPIResponse} from './types' import {type FeedAPI, type FeedAPIResponse} from './types'
type GetActorLikesParams = XrpcRequestParams<
typeof app.bsky.feed.getActorLikes.main
>
export class LikesFeedAPI implements FeedAPI { export class LikesFeedAPI implements FeedAPI {
agent: AtpAgent client: Client
params: GetActorLikes.QueryParams params: GetActorLikesParams
constructor({ constructor({
agent, client,
feedParams, feedParams,
}: { }: {
agent: AtpAgent client: Client
feedParams: GetActorLikes.QueryParams feedParams: GetActorLikesParams
}) { }) {
this.agent = agent this.client = client
this.params = feedParams this.params = feedParams
} }
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.getActorLikes({ const data = await this.client.call(app.bsky.feed.getActorLikes, {
...this.params, ...this.params,
limit: 1, limit: 1,
}) })
return res.data.feed[0] return data.feed[0]
} }
async fetch({ async fetch({
@@ -36,21 +38,22 @@ export class LikesFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): 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, ...this.params,
cursor, cursor,
limit, limit,
}) })
if (res.success) {
// HACKFIX: the API incorrectly returns a cursor when there are no items -sfn // HACKFIX: the API incorrectly returns a cursor when there are no items -sfn
const isEmptyPage = res.data.feed.length === 0 const isEmptyPage = data.feed.length === 0
return { return {
cursor: isEmptyPage ? undefined : res.data.cursor, cursor: isEmptyPage ? undefined : data.cursor,
feed: res.data.feed, feed: data.feed,
}
}
return {
feed: [],
} }
} }
} }
+24 -21
View File
@@ -1,32 +1,34 @@
import { import {type AppBskyFeedDefs} from '@atproto/api'
type Agent, import {type Client, type XrpcRequestParams} from '@atproto/lex'
type AppBskyFeedDefs,
type AppBskyFeedGetListFeed as GetListFeed,
} from '@atproto/api'
import {app} from '#/lexicons'
import {type FeedAPI, type FeedAPIResponse} from './types' import {type FeedAPI, type FeedAPIResponse} from './types'
type GetListFeedParams = XrpcRequestParams<
typeof app.bsky.feed.getListFeed.main
>
export class ListFeedAPI implements FeedAPI { export class ListFeedAPI implements FeedAPI {
agent: Agent client: Client
params: GetListFeed.QueryParams params: GetListFeedParams
constructor({ constructor({
agent, client,
feedParams, feedParams,
}: { }: {
agent: Agent client: Client
feedParams: GetListFeed.QueryParams feedParams: GetListFeedParams
}) { }) {
this.agent = agent this.client = client
this.params = feedParams this.params = feedParams
} }
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { 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, ...this.params,
limit: 1, limit: 1,
}) })
return res.data.feed[0] return data.feed[0]
} }
async fetch({ async fetch({
@@ -36,19 +38,20 @@ export class ListFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): 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, ...this.params,
cursor, cursor,
limit, limit,
}) })
if (res.success) {
return { return {
cursor: res.data.cursor, cursor: data.cursor,
feed: res.data.feed, feed: data.feed,
}
}
return {
feed: [],
} }
} }
} }
+20 -19
View File
@@ -1,25 +1,25 @@
import { import {type AppBskyFeedDefs} from '@atproto/api'
type Agent, import {type Client, type XrpcRequestParams} from '@atproto/lex'
type AppBskyFeedDefs,
type AppBskyFeedGetPosts,
} from '@atproto/api'
import {logger} from '#/logger' import {logger} from '#/logger'
import {app} from '#/lexicons'
import {type FeedAPI, type FeedAPIResponse} from './types' import {type FeedAPI, type FeedAPIResponse} from './types'
type GetPostsParams = XrpcRequestParams<typeof app.bsky.feed.getPosts.main>
export class PostListFeedAPI implements FeedAPI { export class PostListFeedAPI implements FeedAPI {
agent: Agent client: Client
params: AppBskyFeedGetPosts.QueryParams params: GetPostsParams
peek: AppBskyFeedDefs.FeedViewPost | null = null peek: AppBskyFeedDefs.FeedViewPost | null = null
constructor({ constructor({
agent, client,
feedParams, feedParams,
}: { }: {
agent: Agent client: Client
feedParams: AppBskyFeedGetPosts.QueryParams feedParams: GetPostsParams
}) { }) {
this.agent = agent this.client = client
if (feedParams.uris.length > 25) { if (feedParams.uris.length > 25) {
logger.warn( logger.warn(
`Too many URIs provided - expected 25, got ${feedParams.uris.length}`, `Too many URIs provided - expected 25, got ${feedParams.uris.length}`,
@@ -36,17 +36,18 @@ export class PostListFeedAPI implements FeedAPI {
} }
async fetch({}: {}): Promise<FeedAPIResponse> { 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, ...this.params,
}) })
if (res.success) { this.peek = {post: data.posts[0]}
this.peek = {post: res.data.posts[0]}
return { return {
feed: res.data.posts.map(post => ({post})), feed: data.posts.map(post => ({post})),
}
}
return {
feed: [],
} }
} }
} }