Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168import { atUriOf, nsidOf, recordKeyOf, type AtUri, type Cid, type Did, type LabelKey, type Rkey} from "$lib/api/syntax";import { now as tidNow } from "@atcute/tid";import type { OAuthUserAgent } from "@atcute/oauth-browser-client";import { deleteRecord, putRecord } from "$lib/api/write";import { attest } from "$lib/api/attestation";import { awaitReadable, awaitRemoved } from "$lib/api/awaitRecord";import { writeTargetFor } from "$lib/api/knot";import { eraseCob, withRkey, type Written } from "$lib/api/cob";import { createTicket, editTicket as restateOnKnot, TICKET_COLLECTION, type TicketProse} from "$lib/api/ticket";import type { BobbinContext, XrpcRequestInit } from "$lib/api/client";import { jsonGet } from "$lib/api/_request";import type { RecordList, RecordView, TicketRecord, TicketState } from "$lib/api/records";import type * as ShTangledRepoIssue from "$lib/api/lexicons/types/sh/tangled/repo/issue";import type * as ShTangledRepoIssueState from "$lib/api/lexicons/types/sh/tangled/repo/issue/state";
export type IssueRecord = ShTangledRepoIssue.Main;export type IssueStateRecord = ShTangledRepoIssueState.Main;
const ISSUE_COLLECTION = nsidOf("sh.tangled.repo.issue");
export interface TicketInput { title: string; body?: string; targetBranch?: string; tangledLabels?: LabelKey[];}
const legacyIssue = (repoDid: Did, { title, body }: TicketInput): IssueRecord => ({ $type: "sh.tangled.repo.issue" as IssueRecord["$type"], repo: repoDid as IssueRecord["repo"], title, body: body ?? "", createdAt: new Date().toISOString() }) as IssueRecord;
const proseOf = ({ title, body, targetBranch, tangledLabels }: TicketInput): TicketProse => ({ title, body: body ?? "", targetBranch, tangledLabels});
export const openTicket = async ( agent: OAuthUserAgent, bobbinUrl: string | undefined, repoDid: Did, input: TicketInput): Promise<Written> => { const target = await writeTargetFor(bobbinUrl, repoDid); if (target.kind === "knot") { const written = withRkey(await createTicket(agent, target.knot, repoDid, proseOf(input))); attest(agent, written, target.knot); return written; } const saved = await putRecord( agent, ISSUE_COLLECTION, recordKeyOf(tidNow()), legacyIssue(repoDid, input) ); await awaitReadable(bobbinUrl, saved); return withRkey(saved);};
export const editTicket = async ( agent: OAuthUserAgent, bobbinUrl: string | undefined, repoDid: Did, input: TicketInput & { rkey: Rkey; swapRecord: Cid }): Promise<Written> => { const target = await writeTargetFor(bobbinUrl, repoDid); if (target.kind === "knot") { const uri = atUriOf(`at://${repoDid}/${TICKET_COLLECTION}/${input.rkey}`); return withRkey(await restateOnKnot(agent, target.knot, uri, proseOf(input))); } const saved = await putRecord( agent, ISSUE_COLLECTION, input.rkey, legacyIssue(repoDid, input), input.swapRecord ); await awaitReadable(bobbinUrl, saved); return withRkey(saved);};
export const removeTicket = async ( agent: OAuthUserAgent, bobbinUrl: string | undefined, repoDid: Did, input: { rkey: Rkey; swapRecord: Cid }): Promise<{ uri: AtUri }> => { const target = await writeTargetFor(bobbinUrl, repoDid); const uri = atUriOf(`at://${repoDid}/${TICKET_COLLECTION}/${input.rkey}`); if (target.kind === "knot") await eraseCob(agent, target.knot, { uri, cid: input.swapRecord }); else { await deleteRecord(agent, ISSUE_COLLECTION, input.rkey, input.swapRecord); await awaitRemoved(bobbinUrl, uri); } return { uri };};
export const getIssue = (ctx: BobbinContext, issue: AtUri, init?: XrpcRequestInit) => jsonGet<RecordView<IssueRecord>>(ctx, "sh.tangled.repo.getIssue", { issue }, init);
export const getIssues = (ctx: BobbinContext, issues: readonly AtUri[], init?: XrpcRequestInit) => jsonGet<RecordList<IssueRecord>>(ctx, "sh.tangled.repo.getIssues", { issues }, init);
export type IssueState = "open" | "closed";
// a listIssues row: the embedded issue record plus bobbin-derived state/counts.export interface IssueListItem { cid?: string; commentCount: number; state: TicketState; stateUpdatedAt?: string; uri: string; value: TicketRecord;}
export interface IssueListPage { cursor?: string | null; total?: number; items: IssueListItem[];}
export interface ListIssuesFilter { state?: TicketState; author?: string; limit?: number; cursor?: string; order?: "asc" | "desc";}
export const listIssues = ( ctx: BobbinContext, subject: Did, filter: ListIssuesFilter = {}, init?: XrpcRequestInit) => jsonGet<IssueListPage>(ctx, "sh.tangled.repo.listIssues", { subject, ...filter }, init);
export interface IssueStateList { cursor?: string | null; items: RecordView<IssueStateRecord>[];}
export const listIssueStates = ( ctx: BobbinContext, subject: AtUri, filter: { limit?: number; cursor?: string; order?: "asc" | "desc" } = {}, init?: XrpcRequestInit) => jsonGet<IssueStateList>(ctx, "sh.tangled.repo.issue.listStates", { subject, ...filter }, init);