UNPKG

@atcute/car

Version:

lightweight DASL CAR and atproto repository decoder for AT Protocol.

143 lines 5.84 kB
import Queue from 'yocto-queue'; import * as CBOR from '@atcute/cbor'; import * as CID from '@atcute/cid'; import { decodeUtf8From } from '@atcute/uint8array'; import * as CarReader from '../car-reader/index.js'; import { assert } from '../utils.js'; import { isCommit, isMstNode } from './mst.js'; import { RepoEntry } from './types.js'; export const repoEntryTransform = () => { const transform = new TransformStream(); let repo; return { readable: new ReadableStream({ async start(controller) { repo = fromStream(transform.readable); try { for await (const entry of repo) { controller.enqueue(entry); } await repo.dispose(); controller.close(); } catch (err) { controller.error(err); } }, async cancel() { if (repo !== undefined) { await repo.dispose(); } }, }), writable: transform.writable, }; }; export const fromStream = (stream) => { let missingBlocks = []; return { get missingBlocks() { return missingBlocks; }, async dispose() { // does nothing for now }, [Symbol.asyncDispose]() { return this.dispose(); }, async *[Symbol.asyncIterator]() { // await using car = CarReader.fromStream(stream); const car = CarReader.fromStream(stream); try { const pending = new Map(); const strays = new Map(); const queue = new Queue(); const request = (cid, meta) => { const entry = strays.get(cid); if (entry !== undefined) { strays.delete(cid); queue.enqueue({ c: cid, e: entry, m: meta }); } else { pending.set(cid, meta); } }; { const roots = await car.roots(); assert(roots.length === 1, `expected only 1 root in the car archive; got=${roots.length}`); const rootCid = roots[0].$link; request(rootCid, { t: 0 }); } for await (const entry of car) { const cid = CID.toString(entry.cid); { const meta = pending.get(cid); if (meta !== undefined) { pending.delete(cid); queue.enqueue({ c: cid, e: entry, m: meta }); } else { strays.set(cid, entry); } } let task; while ((task = queue.dequeue())) { const { c: cid, e: entry, m: meta } = task; switch (meta.t) { case 0: { const commit = CBOR.decode(entry.bytes); assert(isCommit(commit), `expected commit block; cid=${cid}`); request(commit.data.$link, { t: 1 }); break; } case 1: { const node = CBOR.decode(entry.bytes); assert(isMstNode(node), `expected mst node block; cid=${cid}`); const entries = node.e; const left = node.l; let lastKey = ''; if (left !== null) { request(left.$link, meta); } for (let i = 0, il = entries.length; i < il; i++) { const entry = entries[i]; const next = entry.t; const key_str = decodeUtf8From(CBOR.fromBytes(entry.k)); const key = lastKey.slice(0, entry.p) + key_str; lastKey = key; request(entry.v.$link, { t: 2, k: key }); if (next !== null) { request(next.$link, { t: 1 }); } } break; } case 2: { const [collection, rkey] = meta.k.split('/'); yield new RepoEntry(collection, rkey, CID.toCidLink(entry.cid), entry); break; } } } } missingBlocks = Array.from(pending, ([cid, meta]) => { switch (meta.t) { case 0: { return { cid, type: 'commit' }; } case 1: { return { cid, type: 'mst-node' }; } case 2: { return { cid, type: 'record', key: meta.k }; } } }); } finally { await car.dispose(); } }, }; }; //# sourceMappingURL=stream-repo-reader.js.map