Skip to content

Commit

Permalink
post view hydration
Browse files Browse the repository at this point in the history
  • Loading branch information
devinivy committed Dec 7, 2023
1 parent 0148a65 commit a49f483
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 4 deletions.
103 changes: 100 additions & 3 deletions packages/bsky/src/hydration/hydrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { AtUri } from '@atproto/syntax'
import { DataPlaneClient } from '../data-plane/client'
import { Notification } from '../data-plane/gen/bsky_pb'
import { ids } from '../lexicon/lexicons'
import { isMain as isEmbedRecord } from '../lexicon/types/app/bsky/embed/record'
import { isMain as isEmbedRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia'

import {
ActorHydrator,
ProfileAggs,
Expand All @@ -19,13 +22,15 @@ import {
FeedHydrator,
Likes,
Posts,
Threadgates,
} from './feed'

export type HydrationState = {
actors?: Actors
profileViewers?: ProfileViewerStates
profileAggs?: ProfileAggs
posts?: Posts
threadgates?: Threadgates
lists?: Lists
listViewers?: ListViewerStates
listItems?: ListItems
Expand Down Expand Up @@ -149,11 +154,61 @@ export class Hydrator {
}

// app.bsky.feed.defs#postView
// @TODO handle 3p blocks
// - post
// - profile
// - list basic
// - list
// - profile
// - list basic
// - feedgen
// - profile
// - list basic
async hydratePosts(
uris: string[],
viewer: string | null,
): Promise<HydrationState> {
throw new Error('not implemented')
const postsLayer0 = await this.feed.getPosts(uris)
// first level embeds
const urisLayer1 = nestedRecordUris(postsLayer0)
const urisLayer1ByCollection = urisByCollection(urisLayer1)
const postUrisLayer1 = urisLayer1ByCollection.get(ids.AppBskyFeedPost) ?? []
const postsLayer1 = await this.feed.getPosts(postUrisLayer1)
// second level embeds
const urisLayer2 = nestedRecordUris(postsLayer1)
const urisLayer2ByCollection = urisByCollection(urisLayer2)
// collect remaining post embeds, list/feedgen embeds, post record hydration
const postUrisLayer2 = urisLayer2ByCollection.get(ids.AppBskyFeedPost) ?? []
const nestedListUris = [
...(urisLayer1ByCollection.get(ids.AppBskyGraphList) ?? []),
...(urisLayer2ByCollection.get(ids.AppBskyGraphList) ?? []),
]
const nestedFeedGenUris = [
...(urisLayer1ByCollection.get(ids.AppBskyFeedGenerator) ?? []),
...(urisLayer2ByCollection.get(ids.AppBskyFeedGenerator) ?? []),
]
const allPostUris = [...uris, ...postUrisLayer1, ...postUrisLayer2]
const [
postsLayer2,
labels,
threadgates,
profileState,
listState,
feedGenState,
] = await Promise.all([
this.feed.getPosts(postUrisLayer2),
this.label.getLabelsForSubjects(allPostUris),
this.feed.getThreadgatesForPosts(allPostUris),
this.hydrateProfiles(allPostUris.map(didFromUri), viewer),
this.hydrateLists(nestedListUris, viewer),
this.hydrateFeedGens(nestedFeedGenUris, viewer),
])
// combine all hydration state
return mergeManyStates(profileState, listState, feedGenState, {
posts: mergeManyMaps(postsLayer0, postsLayer1, postsLayer2),
labels,
threadgates,
})
}

// app.bsky.feed.defs#feedViewPost
Expand Down Expand Up @@ -244,14 +299,41 @@ const listUrisFromProfileViewer = (item: ProfileViewerState | null) => {
const labelSubjectsForDid = (dids: string[]) => {
return [
...dids,
...dids.map((did) => `at://${did}/${ids.AppBskyActorProfile}/self`),
...dids.map((did) =>
AtUri.make(did, ids.AppBskyActorProfile, 'self').toString(),
),
]
}

const didFromUri = (uri: string) => {
return new AtUri(uri).hostname
}

const nestedRecordUris = (posts: Posts): string[] => {
const uris: string[] = []
for (const item of posts.values()) {
const post = item?.record
if (!post?.embed) continue
if (isEmbedRecord(post.embed)) {
uris.push(post.embed.record.uri)
} else if (isEmbedRecordWithMedia(post.embed)) {
uris.push(post.embed.record.record.uri)
}
}
return uris
}

const urisByCollection = (uris: string[]): Map<string, string[]> => {
const result = new Map<string, string[]>()
for (const uri of uris) {
const collection = new AtUri(uri).collection
const items = result.get(collection) ?? []
items.push(uri)
result.set(collection, items)
}
return result
}

const mergeStates = (
stateA: HydrationState,
stateB: HydrationState,
Expand All @@ -272,8 +354,23 @@ const mergeStates = (
}
}

const mergeMaps = <T>(mapA?: HydrationMap<T>, mapB?: HydrationMap<T>) => {
const mergeMaps = <T>(
mapA?: HydrationMap<T>,
mapB?: HydrationMap<T>,
): HydrationMap<T> | undefined => {
if (!mapA) return mapB
if (!mapB) return mapA
return mapA.merge(mapB)
}

const mergeManyStates = (...states: HydrationState[]) => {
const initial: HydrationState = {}
return states.reduce((acc, state) => mergeStates(acc, state), initial)
}

const mergeManyMaps = <T>(...maps: HydrationMap<T>[]) => {
return maps.reduce(
(acc, map) => mergeMaps(acc, map),
undefined as HydrationMap<T> | undefined,
)
}
2 changes: 1 addition & 1 deletion packages/bsky/src/hydration/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class LabelHydrator {

async getLabelsForSubjects(
subjects: string[],
issuers: string[],
issuers?: string[],
): Promise<Labels> {
const res = await this.dataplane.getLabels({ subjects, issuers })
return res.labels.reduce((acc, cur) => {
Expand Down

0 comments on commit a49f483

Please sign in to comment.