Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111import { ok } from '@atcute/client';import { mainSchema as createRecordSchema } from '@atcute/atproto/types/repo/createRecord';import { mainSchema as deleteRecordSchema } from '@atcute/atproto/types/repo/deleteRecord';import type { Did, Nsid, RecordKey } from '@atcute/lexicons/syntax';import type { OAuthUserAgent } from '@atcute/oauth-browser-client';import { createClient } from '$lib/auth/agent';import type { BobbinContext } from './client';import { items } from './pagination';import { rkeyFromUri } from './uri';import type * as ShTangledGraphFollow from './lexicons/types/sh/tangled/graph/follow';import type * as ShTangledGraphVouch from './lexicons/types/sh/tangled/graph/vouch';import type * as ShTangledFeedStar from './lexicons/types/sh/tangled/feed/star';
export type FollowRecord = ShTangledGraphFollow.Main;export type VouchRecord = ShTangledGraphVouch.Main;export type StarRecord = ShTangledFeedStar.Main;
const FOLLOW_COLLECTION = 'sh.tangled.graph.follow' as Nsid;const STAR_COLLECTION = 'sh.tangled.feed.star' as Nsid;
// TODO(bobbin): needs a relation point-lookup (e.g. graph.getFollow?actor=&subject=)// or a `viewer` hydration param on lists; scanning listFollowsBy pages is O(follows).export const findFollowRkey = async ( ctx: BobbinContext, viewerDid: string, subject: string, options: { maxPages?: number } = {}): Promise<string | null> => { for await (const item of items( ctx, 'sh.tangled.graph.listFollowsBy', { subject: viewerDid as Did }, { maxPages: options.maxPages ?? 10 } )) { if ((item.value as FollowRecord).subject === subject) return rkeyFromUri(item.uri); } return null;};
const createGenericRecord = async <T extends { $type: string }>( agent: OAuthUserAgent, collection: Nsid, record: T): Promise<string> => { const rpc = createClient(agent); const result = await ok( rpc.call(createRecordSchema, { input: { repo: agent.sub, collection, record } }) ); return rkeyFromUri(result.uri);};
const deleteGenericRecord = async ( agent: OAuthUserAgent, collection: Nsid, rkey: string): Promise<void> => { const rpc = createClient(agent); await ok( rpc.call(deleteRecordSchema, { input: { repo: agent.sub, collection, rkey: rkey as RecordKey } }) );};
export const createFollow = (agent: OAuthUserAgent, subject: string): Promise<string> => createGenericRecord(agent, FOLLOW_COLLECTION, { $type: 'sh.tangled.graph.follow', subject: subject as Did, createdAt: new Date().toISOString() });
export const deleteFollow = (agent: OAuthUserAgent, rkey: string): Promise<void> => deleteGenericRecord(agent, FOLLOW_COLLECTION, rkey);
export const createStar = (agent: OAuthUserAgent, repoDid: string): Promise<string> => createGenericRecord(agent, STAR_COLLECTION, { $type: 'sh.tangled.feed.star', subject: { $type: 'sh.tangled.feed.star#repo', did: repoDid as Did }, createdAt: new Date().toISOString() });
export const deleteStar = (agent: OAuthUserAgent, rkey: string): Promise<void> => deleteGenericRecord(agent, STAR_COLLECTION, rkey);
// TODO(bobbin): same relation-lookup gap as findFollowRkey; walking every star of// the viewer to build this map is O(stars) per page load.export const listStarRkeys = async ( ctx: BobbinContext, viewerDid: string, options: { maxPages?: number } = {}): Promise<Map<string, string>> => { const rkeys = new Map<string, string>(); for await (const item of items( ctx, 'sh.tangled.feed.listStarsBy', { subject: viewerDid as Did }, { maxPages: options.maxPages ?? 10 } )) { const value = item.value as StarRecord; if (value.subject.$type === 'sh.tangled.feed.star#repo') { rkeys.set(value.subject.did, rkeyFromUri(item.uri)); } } return rkeys;};