/** * A run the repository's owner asked for, rather than one a push caused. * * The runner listens on the firehose and has no inbound address, so a request * is a record in the owner's repo. Reaching the run needs one more read: the * request names a repository and a ref, and the check needs the repository * record's current version and the commit that ref points at. */ import { GIT_REPO_COLLECTION, parseRepoRecord, rkeyOf } from '@pdsjs/git'; /** * @typedef {Object} CheckRequestRecord * @property {string} repo - record key of the repository to check * @property {string} ref - full ref name * @property {string|null} sha - commit to check, or null for the ref's own */ /** * A request record's fields, or null for a record this runner cannot act on. * A malformed request is ignored rather than thrown: anyone may write any * record, and one bad value must not stop the events behind it. * @param {unknown} value * @returns {CheckRequestRecord|null} */ export function parseCheckRequest(value) { const record = /** @type {Record} */ (value ?? {}); if (typeof record.repo !== 'string' || !record.repo) return null; if (typeof record.ref !== 'string' || !record.ref) return null; const sha = typeof record.sha === 'string' && record.sha ? record.sha : null; return { repo: record.repo, ref: record.ref, sha }; } /** * @typedef {Object} RepoRecordVersion * @property {string} uri * @property {string} cid * @property {string} name - what the repository is called * @property {string} rkey - its record key, which names it everywhere else * @property {Array<{name: string, sha: string}>} refs */ /** * The repository record as it stands now. A request carries no strongref, so * the version a requested run reads is the one this returns. * * The command line names a repository, and a request names its record key, * so this takes either: a key reads one record, and a name is matched * against the account's listing. * @param {Object} target * @param {string} target.service - PDS base URL * @param {string} target.did - repository owner * @param {string} [target.repoName] * @param {string} [target.rkey] * @returns {Promise} */ export async function readRepoRecord(target) { const at = target.rkey ?? target.repoName ?? ''; const params = new URLSearchParams({ repo: target.did, collection: GIT_REPO_COLLECTION, }); if (target.rkey) params.set('rkey', target.rkey); else params.set('limit', '100'); const method = target.rkey ? 'getRecord' : 'listRecords'; const res = await fetch( `${target.service}/xrpc/com.atproto.repo.${method}?${params}`, ); if (!res.ok) { throw new Error(`could not read ${target.did}/${at} (${res.status})`); } const body = /** @type {{uri?: string, cid?: string, value?: unknown, records?: Array<{uri: string, cid: string, value: unknown}>}} */ ( await res.json() ); const row = target.rkey ? body : (body.records ?? []).find( (entry) => parseRepoRecord(entry.value).name === target.repoName, ); if (!row?.uri || !row.cid) { throw new Error(`${target.did}/${at} has no record version`); } const record = parseRepoRecord(row.value); return { uri: row.uri, cid: row.cid, name: record.name, rkey: rkeyOf(row.uri), refs: record.refs, }; } /** * The commit a request names: its own, or the one the ref points at. * @param {CheckRequestRecord} request * @param {Array<{name: string, sha: string}>} refs * @returns {string|null} null when the ref is not in the record */ export function requestedSha(request, refs) { if (request.sha) return request.sha; return refs.find((ref) => ref.name === request.ref)?.sha ?? null; }