chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// In-memory ActorStorage and SharedStorage ports, enough to run a
// PersonalDataServer without a database. Shared by the tests that exercise
// repo mechanics: what a commit carries, and rebuilding the tree.
export function createMemoryStorage() {
/** @type {Map<string, Uint8Array>} */
const blocks = new Map();
/** @type {Map<string, {cid: string, value: Uint8Array}>} */
const records = new Map();
/** @type {Array<{seq: number, cid: string, rev: string, prev: string|null}>} */
const commits = [];
/** @type {Array<{seq: number, commit_cid: string, evt: Uint8Array}>} */
const events = [];
/** @type {Record<string, *>} */
const meta = {};
return {
async getRecord(/** @type {string} */ uri) {
return records.get(uri) || null;
},
async putRecord(
/** @type {string} */ uri,
/** @type {string} */ cid,
/** @type {string} */ _collection,
/** @type {string} */ _rkey,
/** @type {any} */ value,
) {
records.set(uri, { cid, value });
},
async listRecords() {
return { records: [], cursor: null };
},
async listAllRecords() {
return [...records.entries()]
.map(([uri, rec]) => {
const parts = uri.replace('at://', '').split('/');
return { key: `${parts[1]}/${parts[2]}`, cid: rec.cid };
})
.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
},
async deleteRecord(/** @type {string} */ uri) {
records.delete(uri);
},
async getBlock(/** @type {string} */ cid) {
return blocks.get(cid) || null;
},
async putBlock(/** @type {string} */ cid, /** @type {Uint8Array} */ data) {
if (!blocks.has(cid)) blocks.set(cid, data);
},
async deleteBlocks(/** @type {string[]} */ cids) {
for (const cid of cids) blocks.delete(cid);
},
async listBlocks(
/** @type {string|null} */ cursor,
/** @type {number} */ limit,
) {
const page = [...blocks.keys()]
.sort()
.filter((cid) => !cursor || cid > cursor)
.slice(0, limit)
.map((cid) => ({
cid,
data: /** @type {Uint8Array} */ (blocks.get(cid)),
}));
const last = page.length === limit ? page[limit - 1].cid : null;
return { blocks: page, cursor: last };
},
async recordCidsAmong(/** @type {string[]} */ cids) {
const held = new Set([...records.values()].map((r) => r.cid));
return cids.filter((cid) => held.has(cid));
},
async getBlocksCollected() {
return meta.blocksCollected === true;
},
async setBlocksCollected(/** @type {boolean} */ collected) {
meta.blocksCollected = collected;
},
async getLatestCommit() {
return commits.length ? commits[commits.length - 1] : null;
},
async putCommit(
/** @type {number} */ seq,
/** @type {string} */ cid,
/** @type {string} */ rev,
/** @type {string|null} */ prev,
) {
commits.push({ seq, cid, rev, prev });
},
async getEvents() {
return { events, cursor: null };
},
async putEvent(
/** @type {number} */ seq,
/** @type {string} */ _did,
/** @type {string} */ commitCid,
/** @type {Uint8Array} */ evt,
) {
events.push({ seq, commit_cid: commitCid, evt });
},
async getLatestSeq() {
return events.length ? events[events.length - 1].seq : 0;
},
async getBlob() {
return null;
},
async putBlob() {},
async deleteBlob() {},
async listBlobs() {
return { cids: [], cursor: null };
},
async getOrphanedBlobs() {
return [];
},
async linkBlobToRecord() {},
async unlinkBlobsFromRecord() {},
async getDid() {
return meta.did ?? null;
},
async setDid(/** @type {string} */ did) {
meta.did = did;
},
async getPrivateKey() {
return meta.privateKey ?? null;
},
async setPrivateKey(/** @type {any} */ key) {
meta.privateKey = key;
},
async getKemKey() {
return meta.kemKey ?? null;
},
async setKemKey(/** @type {any} */ key) {
meta.kemKey = key;
},
async getHandle() {
return meta.handle ?? null;
},
async setHandle(/** @type {string} */ handle) {
meta.handle = handle;
},
async getAccountStatus() {
return meta.accountStatus ?? null;
},
async setAccountStatus(/** @type {any} */ status) {
meta.accountStatus = status;
},
async getPreferences() {
return [];
},
async setPreferences() {},
_events: events,
_blocks: blocks,
};
}
export function createMemorySharedStorage() {
/** @type {Map<string, *>} */
const actors = new Map();
return {
async putActor(
/** @type {string} */ did,
/** @type {string} */ handle,
/** @type {any} */ createdAt,
) {
actors.set(did, { did, handle, createdAt });
},
async getActor(/** @type {string} */ did) {
return actors.get(did) || null;
},
async resolveHandle() {
return null;
},
async checkAndStoreDpopJti() {
return true;
},
async putOAuthRequest() {},
async getOAuthRequest() {
return null;
},
async deleteOAuthRequest() {},
async putOAuthToken() {},
async getOAuthToken() {
return null;
},
async deleteOAuthToken() {},
};
}