// @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; }