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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
67 lines · 3.2 KB Raw History
// @pdsjs/spaces/routes - the space route table.
//
// @pdsjs/core never imports this package. Instead a platform package builds the
// table and passes it to the PDS as `spaceRoutes`, which merges it into its own
// route map. With the feature off, the routes are simply absent and requests
// fall through to the normal 404 — see spec section 3.

import { createAuthRoutes } from './handlers/auth.js';
import { createManageRoutes } from './handlers/manage.js';
import { createReadRoutes } from './handlers/read.js';
import { createWriteRoutes } from './handlers/write.js';
import { createNotifyOutbox } from './outbox.js';

/**
 * @param {Object} ctx
 * @param {import('@pdsjs/core/ports').SpaceStoragePort} ctx.spaceStorage
 * @param {import('@pdsjs/core/ports').BlobPort} ctx.blobs - blobs referenced
 *   from space records live in the account's ordinary blob store
 * @param {(blobCid: string, recordUri: string, recordTime: number|null) => Promise<void>} ctx.linkBlob
 * @param {(recordUri: string) => Promise<void>} ctx.unlinkBlobs
 * @param {() => Promise<string|null>} ctx.getDid - the hosted account's DID
 * @param {() => Promise<import('./token.js').SpaceSigner>} ctx.getSigner
 *   - the account's atproto signing key, which signs space tokens and
 *   service auth
 * @param {() => Promise<{privateKey: Uint8Array, didKey: string}|null>} ctx.getKemKey
 *   - the account's `#atproto_kem` key, which MACs its repos' responses
 * @param {import('@pdsjs/core/ports').DidResolverPort} ctx.resolveDid - DID document lookup,
 *   injected so this package stays dependency-free
 * @param {import('@pdsjs/core/ports').SignatureVerifierPort} ctx.verifier
 * @param {import('@pdsjs/core/ports').RecordWriteGuardPort} [ctx.recordGuard]
 *   - a veto over writes in its named collections, e.g. git branch protection
 * @param {typeof fetch} [ctx.fetch] - injected for the outbound notification
 *   calls, so tests can observe them without a network
 * @param {(work: Promise<unknown>) => void} [ctx.background] - runs write
 *   notices after the response; absent, they run before it
 * @param {ReturnType<typeof import('./outbox.js').createNotifyOutbox>} [ctx.outbox]
 *   - notices owed to other services; built from ctx when absent
 * @returns {import('@pdsjs/core/pds').Routes}
 */
export function createSpaceRoutes(ctx) {
  if (!ctx?.spaceStorage) {
    throw new Error('createSpaceRoutes requires a spaceStorage port');
  }
  if (!ctx.getDid || !ctx.getSigner || !ctx.getKemKey) {
    throw new Error(
      'createSpaceRoutes requires getDid, getSigner and getKemKey',
    );
  }
  if (!ctx.resolveDid || !ctx.verifier) {
    throw new Error('createSpaceRoutes requires resolveDid and a verifier');
  }
  // Refused rather than defaulted: without the link callbacks a space record's
  // blobs look unreferenced, and orphan cleanup deletes them.
  if (!ctx.blobs || !ctx.linkBlob || !ctx.unlinkBlobs) {
    throw new Error(
      'createSpaceRoutes requires blobs, linkBlob and unlinkBlobs',
    );
  }
  // One outbox, so write notices and deletion notices share a schedule.
  const shared = { ...ctx, outbox: ctx.outbox ?? createNotifyOutbox(ctx) };
  return {
    ...createWriteRoutes(shared),
    ...createReadRoutes(shared),
    ...createAuthRoutes(shared),
    ...createManageRoutes(shared),
  };
}