~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
89 lines · 2.9 KB Raw History
// @pdsjs/spaces/uri - permissioned space AT-URI addressing.
//
//   at://{spaceDid}/space/{spaceType}/{skey}[/{authorDid}/{collection}/{rkey}]
//
// Space URIs are distinguished from public ones by the literal `space` segment,
// which contains no dots, whereas a collection NSID always contains at least two.

export const SPACE_MARKER = 'space';

/**
 * @param {{spaceDid: string, spaceType: string, skey: string,
 *          authorDid?: string|null, collection?: string|null, rkey?: string|null}} parts
 * @returns {string}
 */
export function formatSpaceUri(parts) {
  const { spaceDid, spaceType, skey, authorDid, collection, rkey } = parts;
  const optionalCount = [authorDid, collection, rkey].filter(Boolean).length;
  if (optionalCount !== 0 && optionalCount !== 3) {
    throw new Error(
      'authorDid, collection, and rkey must be all present or all absent',
    );
  }
  let uri = `at://${spaceDid}/${SPACE_MARKER}/${spaceType}/${skey}`;
  if (authorDid) uri += `/${authorDid}`;
  if (collection) uri += `/${collection}`;
  if (rkey) uri += `/${rkey}`;
  return uri;
}

/**
 * @param {string} uri
 * @returns {boolean}
 */
export function isSpaceUri(uri) {
  const segments = splitUri(uri);
  return segments !== null && segments[1] === SPACE_MARKER;
}

/**
 * @param {string} uri
 * @returns {{spaceDid: string, spaceType: string, skey: string,
 *            authorDid: string|null, collection: string|null, rkey: string|null}}
 */
export function parseSpaceUri(uri) {
  const segments = splitUri(uri);
  if (segments === null || segments[1] !== SPACE_MARKER) {
    throw new Error(`not a space URI: ${uri}`);
  }
  const [spaceDid, , spaceType, skey, authorDid, collection, rkey] = segments;
  if (segments.length !== 4 && segments.length !== 7) {
    throw new Error(`invalid space URI: ${uri}`);
  }
  if (!spaceDid.startsWith('did:')) {
    throw new Error(`space authority must be a DID: ${uri}`);
  }
  if (!spaceType.includes('.')) {
    throw new Error(`space type must be an NSID: ${uri}`);
  }
  if (!skey) throw new Error(`invalid space URI: ${uri}`);
  if (segments.length === 4) {
    return {
      spaceDid,
      spaceType,
      skey,
      authorDid: null,
      collection: null,
      rkey: null,
    };
  }
  if (!authorDid.startsWith('did:')) {
    throw new Error(`space record author must be a DID: ${uri}`);
  }
  if (!collection.includes('.')) {
    throw new Error(`collection must be an NSID: ${uri}`);
  }
  if (!rkey) throw new Error(`invalid space URI: ${uri}`);
  return { spaceDid, spaceType, skey, authorDid, collection, rkey };
}

/**
 * @param {string} uri
 * @returns {string[]|null} host followed by path segments, or null if not an at:// URI
 */
function splitUri(uri) {
  if (!uri.startsWith('at://')) return null;
  const segments = uri.slice('at://'.length).split('/');
  if (segments.length < 2 || segments.some((s) => s === '')) return null;
  return segments;
}