chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
/**
* 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) }),
};
};
}