chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
/**
* 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<string, unknown>} */ (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<RepoRecordVersion>}
*/
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;
}