Something went wrong. Try again.
An AT Protocol Personal Data Server written in JavaScript pdsjs.dev
pds atproto
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127// @pdsjs/spaces/admin - space management for the account page.//// The account page runs behind the owner's own session, so these skip the// scope checks the XRPC management endpoints apply to third-party callers.// They mutate the same storage the same way com.atproto.simplespace.* does.
import { cborDecode, createTid } from '@pdsjs/core/repo';import { LtHash } from './lthash.js';import { recordWriterUpdate } from './sequence.js';import { makeSpaceRow } from './space-row.js';import { formatSpaceUri } from './uri.js';import { applyWrites } from './writer.js';
/** * @param {Object} ctx * @param {import('@pdsjs/core/ports').SpaceStoragePort} ctx.spaceStorage * @param {() => Promise<string|null>} ctx.getDid * @returns {import('@pdsjs/core/ports').SpaceAdminPort} */export function createSpaceAdmin({ spaceStorage, getDid }) { /** @returns {Promise<string>} */ async function requireDid() { const did = await getDid(); if (!did) throw new Error('server not initialised'); return did; }
/** * @param {string} space * @returns {Promise<void>} */ async function requireOwnedSpace(space) { const row = await spaceStorage.getSpace(space); if (!row?.isOwner || row.deletedAt) { throw new Error(`no such space: ${space}`); } }
return { async createSpace({ type, skey }) { if (!type.includes('.')) { throw new Error(`space type must be an NSID: ${type}`); } if (skey && skey.length > 512) { throw new Error('skey is too long'); } const did = await requireDid(); const uri = formatSpaceUri({ spaceDid: did, spaceType: type, skey: skey || createTid(), }); if (await spaceStorage.getSpace(uri)) { throw new Error(`space already exists: ${uri}`); } await spaceStorage.putSpace(makeSpaceRow(uri, { isOwner: true })); // The owner joins their own space with both rights: a member-list space // checks membership before it issues a credential, and that is the // default policy. await spaceStorage.putMember(uri, did, { read: true, write: true }); return { uri }; },
async putMember(space, did, access) { if (!did.startsWith('did:')) { throw new Error(`not a DID: ${did}`); } await requireOwnedSpace(space); await spaceStorage.putMember(space, did, access); },
async removeMember(space, did) { const owner = await requireDid(); if (did === owner) { throw new Error('the owner cannot leave their own space'); } await requireOwnedSpace(space); await spaceStorage.removeMember(space, did); },
async getRecord(space, collection, rkey) { await requireOwnedSpace(space); const row = await spaceStorage.getSpaceRecord(space, collection, rkey); return row ? /** @type {Object} */ (cborDecode(row.value)) : null; },
async createRecord(space, collection, rkey, record) { await writeRecord(space, 'create', collection, rkey, record); },
async putRecord(space, collection, rkey, record) { await writeRecord(space, 'put', collection, rkey, record); },
async deleteRecord(space, collection, rkey) { await writeRecord(space, 'delete', collection, rkey); }, };
/** * @param {string} space * @param {'create'|'put'|'delete'} action * @param {string} collection * @param {string} rkey * @param {Object} [record] - absent on a delete * @returns {Promise<void>} */ async function writeRecord(space, action, collection, rkey, record) { const did = await requireDid(); await requireOwnedSpace(space); const commit = await applyWrites(spaceStorage, { space, writes: [{ action, collection, rkey, ...(record ? { record } : {}) }], }); // What fireNotifyWrite does after an XRPC write into the owner's own // space: record the owner in the writer set, so listRepos knows this // repo before its first push. const hash = await new LtHash(commit.setHash).digest(); await recordWriterUpdate(spaceStorage, { space, did, repoRev: commit.rev, hash, }); }}