// 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} */ 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} */ 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} stream * @returns {Promise} */ 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); }); });