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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
121 lines · 4.3 KB Raw History
/**
 * Files stored in the repo, as a server extension: the platform passes the
 * factory to core's `extensions` option and core serves the Drive section's
 * routes under /account/api/drive.
 *
 * This module must stay free of node-only imports; it runs on Cloudflare
 * Workers.
 */

/** @typedef {import('./account.js').DriveSpaceAdmin} DriveSpaceAdmin */
import { cborDecodeJson } from '@pdsjs/core/repo';
import { createDriveAccountRoutes } from './account.js';
import { createDriveBrowser } from './browser.js';
import { DRIVE_FILE_COLLECTION, DRIVE_FOLDER_COLLECTION } from './lexicon.js';
import { createDriveWriter } from './writer.js';

/**
 * The part of the space admin port a drive needs, built on first use over
 * the host's space storage from @pdsjs/spaces, an optional peer imported
 * only then.
 * @param {import('@pdsjs/core/ports').SpaceStoragePort} spaceStorage
 * @param {() => Promise<string|null>} getDid
 * @returns {DriveSpaceAdmin}
 */
function lazySpaceAdmin(spaceStorage, getDid) {
  /** @type {Promise<import('@pdsjs/core/ports').SpaceAdminPort>|null} */
  let admin = null;
  const load = () => {
    admin ??= import('@pdsjs/spaces/admin').then(({ createSpaceAdmin }) =>
      createSpaceAdmin({ spaceStorage, getDid }),
    );
    return admin;
  };
  return {
    createSpace: async (opts) => (await load()).createSpace(opts),
  };
}

/**
 * @param {Object} [options]
 * @param {boolean} [options.upload] - accept uploads (default on); off, the drive lists and downloads only
 * @param {number} [options.maxFileSize] - largest file accepted, in bytes
 * @returns {import('@pdsjs/core/ports').ExtensionFactory}
 */
export function createDriveExtension({ upload = true, maxFileSize } = {}) {
  return (host) => {
    const { actorStorage, blobs, spaceStorage, spaceBrowser } = host;
    const ports = { actorStorage, blobs, getDid: host.getDid };

    // The account page is the owner's own surface, so a drive held in a
    // space reads from storage rather than the authenticated space
    // endpoints.
    /** @type {import('./ports.js').DriveSpaceReader|undefined} */
    const spaceReader =
      spaceBrowser && spaceStorage
        ? {
            listRecords: (space, collection, limit) =>
              spaceBrowser.listRecords(space, collection, limit),
            getRecord: async (space, rkey) => {
              const row = await spaceStorage.getSpaceRecord(
                space,
                DRIVE_FILE_COLLECTION,
                rkey,
              );
              return row
                ? { cid: row.cid, value: cborDecodeJson(row.value) }
                : null;
            },
            getBlob: async (cid) => {
              const did = await host.getDid();
              if (!did) return null;
              const result = await blobs.get(did, cid);
              if (!result) return null;
              return result.data instanceof Uint8Array
                ? result.data
                : new Uint8Array(/** @type {ArrayBuffer} */ (result.data));
            },
          }
        : undefined;

    const browser = createDriveBrowser({ ...ports, spaceReader });
    const writer = upload
      ? createDriveWriter({
          ...ports,
          createCommit: host.createCommit,
          chunkSize: host.blobUploadLimit,
          maxFileSize,
          hasher: host.hasher,
          spaceReader,
          // The spaces package is an optional peer, imported only when a
          // drive is written into a space.
          spaceWrite: spaceStorage
            ? async (space, writes) => {
                const { applyWrites } = await import('@pdsjs/spaces');
                return applyWrites(spaceStorage, {
                  space,
                  writes,
                  owner: await host.getDid(),
                });
              }
            : undefined,
        })
      : null;

    return {
      name: 'drive',
      collections: [DRIVE_FILE_COLLECTION, DRIVE_FOLDER_COLLECTION],
      routes: createDriveAccountRoutes({
        host,
        browser,
        writer,
        spaceBrowser: spaceBrowser ?? null,
        spaceAdmin: spaceStorage
          ? lazySpaceAdmin(spaceStorage, host.getDid)
          : null,
      }),
      features: { drive: true, driveUpload: Boolean(writer) },
      stats: (count) => ({ driveFiles: count(DRIVE_FILE_COLLECTION) }),
    };
  };
}