This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / api / graph.ts
3.7 kB 110 lines
1import { ok } from "@atcute/client"; 2import { mainSchema as createRecordSchema } from "@atcute/atproto/types/repo/createRecord"; 3import { mainSchema as deleteRecordSchema } from "@atcute/atproto/types/repo/deleteRecord"; 4import type { Did, Nsid, RecordKey } from "@atcute/lexicons/syntax"; 5import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; 6import { createClient } from "$lib/auth/agent"; 7import type { BobbinContext } from "./client"; 8import { items } from "./pagination"; 9import { rkeyFromUri } from "./uri"; 10import type * as ShTangledGraphFollow from "./lexicons/types/sh/tangled/graph/follow"; 11import type * as ShTangledGraphVouch from "./lexicons/types/sh/tangled/graph/vouch"; 12import type * as ShTangledFeedStar from "./lexicons/types/sh/tangled/feed/star"; 13 14export type FollowRecord = ShTangledGraphFollow.Main; 15export type VouchRecord = ShTangledGraphVouch.Main; 16export type StarRecord = ShTangledFeedStar.Main; 17 18const FOLLOW_COLLECTION = "sh.tangled.graph.follow" as Nsid; 19const STAR_COLLECTION = "sh.tangled.feed.star" as Nsid; 20 21// TODO(bobbin): needs a relation point-lookup (e.g. graph.getFollow?actor=&subject=) 22// or a `viewer` hydration param on lists; scanning listFollowsBy pages is O(follows). 23export const findFollowRkey = async ( 24 ctx: BobbinContext, 25 viewerDid: string, 26 subject: string, 27 options: { maxPages?: number } = {} 28): Promise<string | null> => { 29 for await (const item of items( 30 ctx, 31 "sh.tangled.graph.listFollowsBy", 32 { subject: viewerDid as Did }, 33 { maxPages: options.maxPages ?? 10 } 34 )) { 35 if ((item.value as FollowRecord).subject === subject) return rkeyFromUri(item.uri); 36 } 37 return null; 38}; 39 40const createGenericRecord = async <T extends { $type: string }>( 41 agent: OAuthUserAgent, 42 collection: Nsid, 43 record: T 44): Promise<string> => { 45 const rpc = createClient(agent); 46 const result = await ok( 47 rpc.call(createRecordSchema, { 48 input: { repo: agent.sub, collection, record } 49 }) 50 ); 51 return rkeyFromUri(result.uri); 52}; 53 54const deleteGenericRecord = async ( 55 agent: OAuthUserAgent, 56 collection: Nsid, 57 rkey: string 58): Promise<void> => { 59 const rpc = createClient(agent); 60 await ok( 61 rpc.call(deleteRecordSchema, { 62 input: { repo: agent.sub, collection, rkey: rkey as RecordKey } 63 }) 64 ); 65}; 66 67export const createFollow = (agent: OAuthUserAgent, subject: string): Promise<string> => 68 createGenericRecord(agent, FOLLOW_COLLECTION, { 69 $type: "sh.tangled.graph.follow", 70 subject: subject as Did, 71 createdAt: new Date().toISOString() 72 }); 73 74export const deleteFollow = (agent: OAuthUserAgent, rkey: string): Promise<void> => 75 deleteGenericRecord(agent, FOLLOW_COLLECTION, rkey); 76 77export const createStar = (agent: OAuthUserAgent, repoDid: string): Promise<string> => 78 createGenericRecord(agent, STAR_COLLECTION, { 79 $type: "sh.tangled.feed.star", 80 subject: { 81 $type: "sh.tangled.feed.star#repo", 82 did: repoDid as Did 83 }, 84 createdAt: new Date().toISOString() 85 }); 86 87export const deleteStar = (agent: OAuthUserAgent, rkey: string): Promise<void> => 88 deleteGenericRecord(agent, STAR_COLLECTION, rkey); 89 90// TODO(bobbin): same relation-lookup gap as findFollowRkey; walking every star of 91// the viewer to build this map is O(stars) per page load. 92export const listStarRkeys = async ( 93 ctx: BobbinContext, 94 viewerDid: string, 95 options: { maxPages?: number } = {} 96): Promise<Map<string, string>> => { 97 const rkeys = new Map<string, string>(); 98 for await (const item of items( 99 ctx, 100 "sh.tangled.feed.listStarsBy", 101 { subject: viewerDid as Did }, 102 { maxPages: options.maxPages ?? 10 } 103 )) { 104 const value = item.value as StarRecord; 105 if (value.subject.$type === "sh.tangled.feed.star#repo") { 106 rkeys.set(value.subject.did, rkeyFromUri(item.uri)); 107 } 108 } 109 return rkeys; 110};