chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// The streamed writer against a real filesystem: bytes staged in `.tmp`,
// filed under the CID at commit, gone on abort.
import { mkdtemp, readdir, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createFsBlobs } from '../src/index.js';
const DID = 'did:plc:writer';
const CID = 'bafkreidemowriter';
/** @type {string} */
let baseDir;
/** @type {ReturnType<typeof createFsBlobs>} */
let blobs;
beforeEach(async () => {
baseDir = await mkdtemp(join(tmpdir(), 'pdsjs-blobs-'));
blobs = createFsBlobs(baseDir);
});
afterEach(async () => {
await rm(baseDir, { recursive: true, force: true });
});
/**
* @returns {Promise<string[]>}
*/
async function tempEntries() {
try {
return await readdir(join(baseDir, DID, '.tmp'));
} catch {
return [];
}
}
describe('createWriter', () => {
it('stages chunks and files them under the cid', async () => {
const writer = await /** @type {any} */ (blobs).createWriter(DID);
await writer.write(new Uint8Array([1, 2, 3]));
await writer.write(new Uint8Array([4, 5]));
await writer.commit(CID, 'video/mp4');
const stored = await blobs.get(DID, CID);
expect(stored?.mimeType).toBe('video/mp4');
expect(stored?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5]));
expect(await tempEntries()).toEqual([]);
});
it('stores an empty blob', async () => {
const writer = await /** @type {any} */ (blobs).createWriter(DID);
await writer.commit(CID, 'application/octet-stream');
const stored = await blobs.get(DID, CID);
expect(stored?.data).toEqual(new Uint8Array(0));
});
it('leaves nothing behind on abort', async () => {
const writer = await /** @type {any} */ (blobs).createWriter(DID);
await writer.write(new Uint8Array([1, 2, 3]));
await writer.abort();
expect(await blobs.get(DID, CID)).toBe(null);
expect(await tempEntries()).toEqual([]);
});
it('a committed blob deletes like any other', async () => {
const writer = await /** @type {any} */ (blobs).createWriter(DID);
await writer.write(new Uint8Array([9]));
await writer.commit(CID, 'image/png');
await blobs.delete(DID, CID);
expect(await blobs.get(DID, CID)).toBe(null);
});
});
/**
* @param {ReadableStream<Uint8Array>} stream
* @returns {Promise<Uint8Array>}
*/
async function drain(stream) {
const reader = stream.getReader();
/** @type {Uint8Array[]} */
const chunks = [];
let size = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
size += value.length;
}
const out = new Uint8Array(size);
let at = 0;
for (const chunk of chunks) {
out.set(chunk, at);
at += chunk.length;
}
return out;
}
describe('getStream', () => {
const DATA = new Uint8Array([10, 11, 12, 13, 14, 15]);
it('streams the whole blob with its type', async () => {
await blobs.put(DID, CID, DATA, 'video/mp4');
const found = await /** @type {any} */ (blobs).getStream(DID, CID);
expect(found.mimeType).toBe('video/mp4');
expect(await drain(found.body)).toEqual(DATA);
});
it('streams an inclusive byte range', async () => {
await blobs.put(DID, CID, DATA, 'video/mp4');
const found = await /** @type {any} */ (blobs).getStream(DID, CID, {
start: 1,
end: 3,
});
expect(await drain(found.body)).toEqual(new Uint8Array([11, 12, 13]));
});
it('answers null for a blob it does not hold', async () => {
expect(await /** @type {any} */ (blobs).getStream(DID, CID)).toBe(null);
});
});