@atcute/car
Version:
lightweight DASL CAR and atproto repository decoder for AT Protocol.
143 lines • 5.84 kB
JavaScript
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