import { ok } from "@atcute/client"; import { mainSchema as putRecordSchema } from "@atcute/atproto/types/repo/putRecord"; import { mainSchema as deleteRecordSchema } from "@atcute/atproto/types/repo/deleteRecord"; import { mainSchema as getRecordSchema } from "@atcute/atproto/types/repo/getRecord"; import type { Nsid, RecordKey } from "@atcute/lexicons/syntax"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { now as tidNow } from "@atcute/tid"; import { createClient, mintServiceAuth, serviceDidForHost } from "$lib/auth/agent"; import { toResponseError } from "./_request"; import type { PullRecord, PullState, PullStatusRecord, RecordView } from "./records"; const PULL_COLLECTION = "sh.tangled.repo.pull" as Nsid; const PULL_STATUS_COLLECTION = "sh.tangled.repo.pull.status" as Nsid; export const putPull = async ( agent: OAuthUserAgent, rkey: string, record: PullRecord ): Promise> => { const rpc = createClient(agent); const { uri, cid } = await ok( rpc.call(putRecordSchema, { input: { repo: agent.sub, collection: PULL_COLLECTION, rkey: rkey as RecordKey, record } }) ); return { uri, cid, value: record }; }; export const editPull = async ( agent: OAuthUserAgent, rkey: string, patch: { title: string; body: string } ): Promise> => { const rpc = createClient(agent); const existing = await ok( rpc.call(getRecordSchema, { params: { repo: agent.sub, collection: PULL_COLLECTION, rkey: rkey as RecordKey } }) ); const record = { ...(existing.value as PullRecord), ...patch }; const { uri, cid } = await ok( rpc.call(putRecordSchema, { input: { repo: agent.sub, collection: PULL_COLLECTION, rkey: rkey as RecordKey, swapRecord: existing.cid, record } }) ); return { uri, cid, value: record }; }; export const deletePull = async (agent: OAuthUserAgent, rkey: string): Promise => { const rpc = createClient(agent); await ok( rpc.call(deleteRecordSchema, { input: { repo: agent.sub, collection: PULL_COLLECTION, rkey: rkey as RecordKey } }) ); }; /** * Build the status record for a pull transition. Statuses are append-only: every transition is * a fresh record at a new tid, and readers take the newest one they trust. */ export const pullStatusRecord = (pull: string, status: PullState): PullStatusRecord => ({ $type: "sh.tangled.repo.pull.status", pull: pull as PullStatusRecord["pull"], status: `sh.tangled.repo.pull.status.${status}`, createdAt: new Date().toISOString() as PullStatusRecord["createdAt"] }); /** Writes into the acting user's repo, not the pull author's. */ export const putPullStatus = async ( agent: OAuthUserAgent, pull: string, status: PullState ): Promise> => { const rpc = createClient(agent); const record = pullStatusRecord(pull, status); const { uri, cid } = await ok( rpc.call(putRecordSchema, { input: { repo: agent.sub, collection: PULL_STATUS_COLLECTION, rkey: tidNow() as RecordKey, record } }) ); return { uri, cid, value: record }; }; const KEEP_COMMIT_NSID = "sh.tangled.git.keepCommit"; export interface KeepCommitInput { /** repo did the commit lives in */ repo: string; /** the commit to pin */ oid: string; /** at-uri of the record that owns the keep ref; the rkey has to be final */ record: string; } export const keepCommit = async ( agent: OAuthUserAgent, knot: string, input: KeepCommitInput, fetch: typeof globalThis.fetch = globalThis.fetch ): Promise<{ commit: string }> => { const token = await mintServiceAuth(agent, { aud: serviceDidForHost(knot), lxm: KEEP_COMMIT_NSID }); const response = await fetch(`https://${knot}/xrpc/${KEEP_COMMIT_NSID}`, { method: "POST", headers: { "content-type": "application/json", accept: "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify({ repo: input.repo, record: input.record, source: { $type: `${KEEP_COMMIT_NSID}#commit`, repo: input.repo, oid: input.oid } }) }); if (!response.ok) throw await toResponseError(response); return (await response.json()) as { commit: string }; };