import { ok } from "@atcute/client"; import type { Did } from "@atcute/lexicons/syntax"; import type { BobbinContext } from "$lib/api/client"; import { mainSchema as listKnotsSchema } from "$lib/api/lexicons/types/sh/tangled/knot/listKnots"; import { mainSchema as listSpindleMembersSchema } from "$lib/api/lexicons/types/sh/tangled/spindle/listMembers"; import { mainSchema as listSpindlesSchema } from "$lib/api/lexicons/types/sh/tangled/spindle/listSpindles"; import { parseResourceUri } from "@atcute/lexicons/syntax"; interface Page { items: T[]; cursor?: string; } interface RecordItem { uri: string; value: unknown; } const allPages = async (load: (cursor?: string) => Promise>): Promise => { const items: T[] = []; let cursor: string | undefined; do { const page = await load(cursor); items.push(...page.items); cursor = page.cursor || undefined; } while (cursor); return items; }; const recordName = (uri: unknown): string | null => { if (typeof uri !== "string") return null; try { return parseResourceUri(uri).rkey ?? null; } catch { return null; } }; const spindleInstance = (value: unknown): string | null => { if (typeof value !== "object" || value === null || !("instance" in value)) return null; return typeof value.instance === "string" && value.instance ? value.instance : null; }; const targetNames = ( seed: string[], owned: RecordItem[], memberships: RecordItem[] = [], memberName: (item: RecordItem) => string | null = () => null ): string[] => { const names = new Set(seed); for (const item of owned) { const name = recordName(item.uri); if (name) names.add(name); } for (const item of memberships) { const name = memberName(item); if (name) names.add(name); } return [...names].sort((a, b) => a.localeCompare(b)); }; // TODO(boltless): list bookmarked repos instead. const SHARED_KNOTS = ["knot2.tngl.boltless.dev"]; export const availableKnots = async (ctx: BobbinContext, did: string): Promise => targetNames( SHARED_KNOTS, await allPages((cursor) => ok( ctx.xrpc.call(listKnotsSchema, { params: { subject: did as Did, limit: 1_000, cursor } }) ) ) ); export const availableSpindles = async (ctx: BobbinContext, did: string): Promise => { const [owned, memberships] = await Promise.all([ allPages((cursor) => ok( ctx.xrpc.call(listSpindlesSchema, { params: { subject: did as Did, limit: 1_000, cursor } }) ) ), allPages((cursor) => ok( ctx.xrpc.call(listSpindleMembersSchema, { params: { subject: did as Did, limit: 1_000, cursor } }) ) ) ]); return targetNames([], owned, memberships, (item) => spindleInstance(item.value)); };