// curators.js — the registry of curation FUNCTIONS favela-web runs to turn a community's mutable
// { curator, params } contract (favela-community's CommunityRoom.curation()) into a feed skeleton
// (architecture.md "Feeds are curation functions": in=firehose/index, out=[post URIs]).
//
// A curator is a PAGER: ({ limit, cursor }) => Promise<{ posts, cursor }> over the appview index. feedgen's
// skeleton() drives it — filtering each page to bsky posts + paginating until `limit` bsky entries. Curators
// only SELECT posts; they never sign or touch the bsky wire, so the whole layer is bsky-NEUTRAL (the output
// is always app.bsky.feed.post URIs). And the read is P2P-native: roster/curation come from a keyless
// community-room replica, posts from the local Hyperbee appview.
//
// Built-ins: roster (WHO — the room members' posts) + subject (a topic/tag feed). NOT-yet-built curators
// (rules, custom/sandboxed) fall back to ROSTER — the safe default that always yields a valid feed — until
// their implementation ships. Adding a curator = a case here; NO schema change (the room stores any curator
// string, so a community can name a future custom curator with no migration).
const EMPTY = () => Promise.resolve({ posts: [], cursor: null })
// roster: the community's members' posts, reverse-chron. params: none. Uses queries.getTimeline({ authors }).
// (This is favela-web's historical implicit behavior — the default.)
function rosterPager ({ roster, queries }) {
if (!roster || !roster.size) return EMPTY
return ({ limit, cursor }) => queries.getTimeline({ authors: roster, limit, cursor })
}
// subject: a TOPICAL feed — posts carrying params.tag across ALL indexed authors (a community can be about a
// subject, not only a member set). params: { tag }. Uses queries.getTagFeed(tag). Missing tag / no tag query
// → empty (a misconfigured subject feed degrades to empty, never throws).
function subjectPager ({ params, queries }) {
const tag = params && params.tag
if (!tag || typeof queries.getTagFeed !== 'function') return EMPTY
return ({ limit, cursor }) => queries.getTagFeed(tag, { limit, cursor })
}
// The curators with a native implementation today (advertised so a config/UI can validate).
export const IMPLEMENTED_CURATORS = ['roster', 'subject']
// Resolve a { curator, params } contract to a pager. ctx = { roster: Set, queries: { getTimeline, getTagFeed } }.
// Unknown / not-yet-built curators degrade to roster (the safe default) rather than erroring the feed.
export function makeCurator (curation, ctx) {
const curator = (curation && curation.curator) || 'roster'
const params = (curation && curation.params) || {}
switch (curator) {
case 'subject': return subjectPager({ params, ...ctx })
case 'roster': return rosterPager(ctx)
default: return rosterPager(ctx) // rules + custom/sandboxed: not built yet → safe roster fallback
}
}
export default { makeCurator, IMPLEMENTED_CURATORS }