chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
/**
* The appview's ExtensionHost, over a remote account and the local index.
*
* Everything `createGitExtension` consumes is here: record reads land on
* the PDS over the wire, blob reads stream from it, discovery answers from
* the jetstream index, and the write guard arrives once the extension has
* been built — the same late binding core's host does, because the guard is
* the extension's own creation.
*/
import { createIndexedDiscovery } from '@pdsjs/appview/discovery';
import { createGitExtension } from '@pdsjs/git/extension';
/** The most a JSON request body may weigh, matching core's own bound. */
const JSON_BODY_LIMIT = 1_000_000;
/**
* @param {Request} request
* @returns {Promise<unknown>} the parsed body, or a 413 Response when it
* is too large; the caller tells the two apart
*/
async function readJsonBounded(request) {
const text = await request.text();
if (new TextEncoder().encode(text).byteLength > JSON_BODY_LIMIT) {
return Response.json(
{
error: 'PayloadTooLarge',
message: `Request body exceeds ${JSON_BODY_LIMIT} bytes`,
},
{ status: 413 },
);
}
return JSON.parse(text);
}
/**
* @param {Object} options
* @param {import('./remote.js').RemoteAccount} options.account - the PDS
* reached over the wire
* @param {import('@pdsjs/appview/store').AppViewStore} options.store - the
* record index discovery reads from
* @param {string} [options.hostname] - this host's own name, for client
* strings
* @param {string} [options.plcUrl] - the directory DIDs resolve against
* @param {number} [options.blobUploadLimit] - the PDS's blob limit, which
* sizes merge uploads
* @param {(request: Request) => Promise<string|null>} [options.sessionDid] -
* the account session a request carries, for the owner-only procedures
* @returns {{host: unknown, extension: import('@pdsjs/core/ports').PdsExtension}}
*/
export function createGitAppViewHost(options) {
const { account, store } = options;
/** @type {import('@pdsjs/core/ports').PdsExtension|null} */
let mounted = null;
/**
* The record storage git writes through, over the wire. The members the
* git package never calls are the ones a remote host cannot serve: the
* PDS keeps its own blocks and event log.
*/
const actorStorage = {
getRecord: (/** @type {string} */ uri) => account.getRecord(uri),
listRecords: (
/** @type {string} */ collection,
/** @type {string|null} */ cursor,
/** @type {number} */ limit,
) => account.listRecords(collection, cursor, limit),
// The PDS re-encodes and commits what putRecord hands it; a separate
// block write has no remote meaning.
putBlock: async () => {},
putRecord: async (
/** @type {string} */ uri,
/** @type {string} */ _cid,
/** @type {string} */ collection,
/** @type {string} */ rkey,
/** @type {Uint8Array} */ value,
) => account.putRecord(uri, collection, rkey, value, null),
// Blobs the record names are linked by the PDS's own write path.
linkBlobToRecord: async () => {},
getBlob: async () => null,
putBlob: async () => {},
getHandle: () => account.getHandle(),
};
const blobs = {
get: (/** @type {string} */ did, /** @type {string} */ cid) =>
account.getBlob(did, cid),
getStream: (
/** @type {string} */ did,
/** @type {string} */ cid,
/** @type {{start: number, end: number}|undefined} */ range,
) => account.getBlobStream(did, cid, range),
put: async (
/** @type {string} */ _did,
/** @type {Uint8Array} */ data,
/** @type {string} */ mimeType,
) => account.uploadBlob(data, mimeType),
delete: async () => {},
};
const host = {
actorStorage,
blobs,
get writeGuard() {
return mounted?.writeGuard;
},
getDid: account.getDid,
xrpc: account.xrpc,
hostname: options.hostname,
plcUrl: options.plcUrl,
blobUploadLimit: options.blobUploadLimit ?? 5_242_880,
readOnly: false,
readAccountSession: async (/** @type {Request} */ request) =>
options.sessionDid ? options.sessionDid(request) : null,
readJsonBounded,
};
mounted = createGitExtension({
http: true,
browse: true,
discovery: createIndexedDiscovery(store),
})(
/** @type {import('@pdsjs/core/ports').ExtensionHost} */ (
/** @type {unknown} */ (host)
),
);
return { host, extension: mounted };
}