UNPKG

@dstanesc/o-o-o-o-o-o-o

Version:

O-O-O-O-O-O-O is a collection of content addressed persistent data structures

626 lines 28.8 kB
var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } }; import { blockIndexFactory } from './block-index'; import bounds from 'binary-search-bounds'; import { memoryBlockStoreFactory } from './block-store'; import { CID } from 'multiformats/cid'; import { CarReader, CarBufferWriter } from '@ipld/car'; import { versionStoreFactory } from './version-store'; import { graphStoreFactory } from './graph-store'; import { Graph } from './graph'; import { OFFSET_INCREMENTS, PropRecordDecoder } from './serde'; import { chunkyStore } from '@dstanesc/store-chunky-bytes'; const { read } = chunkyStore(); const graphPackerFactory = (linkCodec) => { const computeSize = async (cids, fromStore) => { let size = 0; for (const cid of cids) { const bytes = await fromStore.get(cid); size += CarBufferWriter.blockLength({ cid, bytes }); } return size; }; const writeMany = async (cids, fromStore, writer) => { for (const cid of cids) { const bytes = await fromStore.get(cid); writer.write({ cid, bytes }); } }; const pushMany = async (cids, fromStore, blocks) => { for (const cid of cids) { const bytes = await fromStore.get(cid); blocks.add({ cid, bytes }); } }; const recordOffsets = (firstOffset, recordCount, recordIncrement) => { const offsets = []; for (let i = 0; i < recordCount; i++) { offsets.push(firstOffset + i * recordIncrement); } return offsets; }; const readPropValueRefs = async (propOffsetStart, propCount, index, fromStore) => { const { propRoot, propIndex } = index; const bytes = await read(propOffsetStart, propCount * OFFSET_INCREMENTS.PROP_INCREMENT, { root: propRoot, index: propIndex, decode: linkCodec.decode, get: fromStore.get, }); const propRecords = await new PropRecordDecoder(bytes).read(); return propRecords.map((propRecord) => propRecord.valueRef); }; const findSlices = (valueRefs) => { if (valueRefs.length === 0) return []; let startOffset = valueRefs[0].ref; let endOffset = startOffset + valueRefs[0].length; const ranges = []; for (let i = 1; i < valueRefs.length; i++) { const valueRef = valueRefs[i]; if (valueRef.ref === endOffset) { endOffset = valueRef.ref + valueRef.length; } else { ranges.push([startOffset, endOffset]); startOffset = valueRef.ref; endOffset = startOffset + valueRef.length; } } ranges.push([startOffset, endOffset]); return ranges.map(([start, end]) => { return { startOffset: start, length: end - start }; }); }; const identifyValueBlocks = async (startOffsets, valueRefs, valueArraySize) => { const slices = findSlices(valueRefs); const result = []; for (const slice of slices) { const blocks = await identifyBlocks(startOffsets, slice, valueArraySize); result.push(...blocks); } return result; }; const relevantChunks = (startOffsetArray, startOffset, endOffset, pad) => { return startOffsetArray.slice(bounds.le(startOffsetArray, startOffset), bounds.ge(startOffsetArray, endOffset) + pad); }; const identifyBlocks = async (startOffsets, slice, byteArraySize) => { const startOffset = slice.startOffset; const endOffset = startOffset + slice.length; if (startOffset > byteArraySize) throw new Error(`Start offset out of range ${startOffset} > buffer size ${byteArraySize}`); if (endOffset > byteArraySize) throw new Error(`End offset out of range ${endOffset} > buffer size ${byteArraySize}`); const startOffsetsIndexed = startOffsets; const startOffsetArray = Array.from(startOffsetsIndexed.keys()); const selectedChunks = relevantChunks(startOffsetArray, startOffset, endOffset, 1); return selectedChunks.map((chunkOffset) => startOffsetsIndexed.get(chunkOffset)); }; const computeRecordsSize = async (startOffsets, recordOffset, recordCount, recordSize, recordArraySize, blocksChecked, fromStore) => { const offsets = recordOffsets(recordOffset, recordCount, recordSize); let size = 0; for (const offset of offsets) { const cids = await identifyBlocks(startOffsets, { startOffset: offset, length: recordSize }, recordArraySize); for (const cid of cids) { if (!blocksChecked.has(cid.toString())) { size += await computeSize([cid], fromStore); blocksChecked.add(cid.toString()); } } } return size; }; const computeValueSize = async (startOffsets, valueRefs, valueArraySize, blocksChecked, fromStore) => { const cids = await identifyValueBlocks(startOffsets, valueRefs, valueArraySize); let size = 0; for (const cid of cids) { if (!blocksChecked.has(cid.toString())) { size += await computeSize([cid], fromStore); blocksChecked.add(cid.toString()); } } return size; }; const pushRecords = async (startOffsets, recordOffset, recordCount, recordSize, recordArraySize, blocksAdded, blocks, fromStore) => { const offsets = recordOffsets(recordOffset, recordCount, recordSize); for (const offset of offsets) { const cids = await identifyBlocks(startOffsets, { startOffset: offset, length: recordSize }, recordArraySize); for (const cid of cids) { if (!blocksAdded.has(cid.toString())) { await pushMany([cid], fromStore, blocks); blocksAdded.add(cid.toString()); } } } }; const pushValues = async (startOffsets, valueRefs, valueArraySize, blocksAdded, blocks, fromStore) => { const cids = await identifyValueBlocks(startOffsets, valueRefs, valueArraySize); for (const cid of cids) { if (!blocksAdded.has(cid.toString())) { await pushMany([cid], fromStore, blocks); blocksAdded.add(cid.toString()); } } }; const packCommit = async ({ root, index, blocks, }) => { const { vertexRoot, edgeRoot, propRoot, valueRoot, indexRoot } = index; const roots = [vertexRoot, edgeRoot, propRoot, valueRoot, indexRoot]; const carRoots = [CID.asCID(root)]; let bufferSize = CarBufferWriter.headerLength({ roots: carRoots }); for (const block of blocks) { bufferSize += CarBufferWriter.blockLength({ cid: CID.asCID(block.cid), bytes: block.bytes, }); } const buffer = new Uint8Array(bufferSize); const writer = CarBufferWriter.createWriter(buffer, { roots: carRoots, }); for (const block of blocks) { writer.write({ cid: CID.asCID(block.cid), bytes: block.bytes, }); } const bytes = writer.close(); const cid = await linkCodec.encode(bytes); return { cid, bytes }; }; const packVersionStore = async (versionStoreRoot, fromStore, chunk, valueCodec) => { const versionStore = await versionStoreFactory({ storeRoot: versionStoreRoot, chunk, linkCodec, valueCodec, blockStore: fromStore, }); const { index: versionStoreIndex } = await versionStore.blocksExtract(); const carRoots = [CID.asCID(versionStoreRoot)]; let bufferSize = CarBufferWriter.headerLength({ roots: carRoots }); bufferSize += await computeSize([versionStoreRoot], fromStore); const { startOffsets } = versionStoreIndex.indexStruct; bufferSize += await computeSize(startOffsets.values(), fromStore); const buffer = new Uint8Array(bufferSize); const writer = CarBufferWriter.createWriter(buffer, { roots: carRoots, }); await writeMany(carRoots, fromStore, writer); await writeMany(startOffsets.values(), fromStore, writer); const bytes = writer.close(); const cid = await linkCodec.encode(bytes); return { cid, bytes }; }; const packGraphVersion = async (versionRoot, fromStore) => { return await packGraph(versionRoot, fromStore); }; const packGraph = async (versionRoot, fromStore) => { const { buildRootIndex } = blockIndexFactory({ linkCodec, blockStore: fromStore, }); const { index } = await buildRootIndex(versionRoot); const { vertexIndex, edgeIndex, propIndex, valueIndex, indexIndex, vertexRoot, edgeRoot, propRoot, valueRoot, indexRoot, } = index; const indexes = [ vertexIndex, edgeIndex, propIndex, valueIndex, indexIndex, ]; const roots = [vertexRoot, edgeRoot, propRoot, valueRoot, indexRoot]; const carRoots = [CID.asCID(versionRoot)]; let bufferSize = CarBufferWriter.headerLength({ roots: carRoots }); bufferSize += await computeSize([versionRoot], fromStore); bufferSize += await computeSize(roots, fromStore); for (const idx of indexes) { const { startOffsets } = idx.indexStruct; bufferSize += await computeSize(startOffsets.values(), fromStore); } const buffer = new Uint8Array(bufferSize); const writer = CarBufferWriter.createWriter(buffer, { roots: carRoots, }); await writeMany(carRoots, fromStore, writer); await writeMany(roots, fromStore, writer); for (const idx of indexes) { const { startOffsets } = idx.indexStruct; await writeMany(startOffsets.values(), fromStore, writer); } const bytes = writer.close(); const cid = await linkCodec.encode(bytes); return { cid, bytes }; }; const packRootIndex = async (versionRoot, fromStore) => { const { buildRootIndex } = blockIndexFactory({ linkCodec, blockStore: fromStore, }); const { index } = await buildRootIndex(versionRoot); const { vertexIndex, edgeIndex, propIndex, valueIndex, indexIndex, vertexRoot, edgeRoot, propRoot, valueRoot, indexRoot, } = index; const roots = [vertexRoot, edgeRoot, propRoot, valueRoot, indexRoot]; const carRoots = [CID.asCID(versionRoot)]; let bufferSize = CarBufferWriter.headerLength({ roots: carRoots }); bufferSize += await computeSize([versionRoot], fromStore); bufferSize += await computeSize(roots, fromStore); const buffer = new Uint8Array(bufferSize); const writer = CarBufferWriter.createWriter(buffer, { roots: carRoots, }); await writeMany(carRoots, fromStore, writer); await writeMany(roots, fromStore, writer); const bytes = writer.close(); const cid = await linkCodec.encode(bytes); return { cid, bytes }; }; const packRandom = async (index, vertices, edges, props, fromStore, blocksChecked, blocksAdded, blocks) => { const { vertexIndex, edgeIndex, propIndex, valueIndex } = index; let bufferSize = 0; for (const vertex of vertices) { bufferSize += await computeRecordsSize(vertexIndex.indexStruct.startOffsets, vertex.offset, 1, OFFSET_INCREMENTS.VERTEX_INCREMENT, vertexIndex.indexStruct.byteArraySize, blocksChecked, fromStore); } for (const edge of edges) { bufferSize += await computeRecordsSize(edgeIndex.indexStruct.startOffsets, edge.offset, 1, OFFSET_INCREMENTS.EDGE_INCREMENT, edgeIndex.indexStruct.byteArraySize, blocksChecked, fromStore); } for (const prop of props) { bufferSize += await computeRecordsSize(propIndex.indexStruct.startOffsets, prop.offset, 1, OFFSET_INCREMENTS.PROP_INCREMENT, propIndex.indexStruct.byteArraySize, blocksChecked, fromStore); } for (const prop of props) { const valueRefs = await readPropValueRefs(prop.offset, 1, index, fromStore); bufferSize += await computeValueSize(valueIndex.indexStruct.startOffsets, valueRefs, valueIndex.indexStruct.byteArraySize, blocksChecked, fromStore); } for (const vertex of vertices) { await pushRecords(vertexIndex.indexStruct.startOffsets, vertex.offset, 1, OFFSET_INCREMENTS.VERTEX_INCREMENT, vertexIndex.indexStruct.byteArraySize, blocksAdded, blocks, fromStore); } for (const edge of edges) { await pushRecords(edgeIndex.indexStruct.startOffsets, edge.offset, 1, OFFSET_INCREMENTS.EDGE_INCREMENT, edgeIndex.indexStruct.byteArraySize, blocksAdded, blocks, fromStore); } for (const prop of props) { await pushRecords(propIndex.indexStruct.startOffsets, prop.offset, 1, OFFSET_INCREMENTS.PROP_INCREMENT, propIndex.indexStruct.byteArraySize, blocksAdded, blocks, fromStore); } for (const prop of props) { const valueRefs = await readPropValueRefs(prop.offset, 1, index, fromStore); await pushValues(valueIndex.indexStruct.startOffsets, valueRefs, valueIndex.indexStruct.byteArraySize, blocksAdded, blocks, fromStore); } return bufferSize; }; const packRange = async (index, vertexOffsetStart, vertexCount, edgeOffsetStart, edgeCount, propOffsetStart, propCount, fromStore, blocksChecked, blocksAdded, blocks) => { const { vertexIndex, edgeIndex, propIndex, valueIndex } = index; const valueRefs = await readPropValueRefs(propOffsetStart, propCount, index, fromStore); let bufferSize = 0; bufferSize += await computeRecordsSize(vertexIndex.indexStruct.startOffsets, vertexOffsetStart, vertexCount, OFFSET_INCREMENTS.VERTEX_INCREMENT, vertexIndex.indexStruct.byteArraySize, blocksChecked, fromStore); bufferSize += await computeRecordsSize(edgeIndex.indexStruct.startOffsets, edgeOffsetStart, edgeCount, OFFSET_INCREMENTS.EDGE_INCREMENT, edgeIndex.indexStruct.byteArraySize, blocksChecked, fromStore); bufferSize += await computeRecordsSize(propIndex.indexStruct.startOffsets, propOffsetStart, propCount, OFFSET_INCREMENTS.PROP_INCREMENT, propIndex.indexStruct.byteArraySize, blocksChecked, fromStore); bufferSize += await computeValueSize(valueIndex.indexStruct.startOffsets, valueRefs, valueIndex.indexStruct.byteArraySize, blocksChecked, fromStore); await pushRecords(vertexIndex.indexStruct.startOffsets, vertexOffsetStart, vertexCount, OFFSET_INCREMENTS.VERTEX_INCREMENT, vertexIndex.indexStruct.byteArraySize, blocksAdded, blocks, fromStore); await pushRecords(edgeIndex.indexStruct.startOffsets, edgeOffsetStart, edgeCount, OFFSET_INCREMENTS.EDGE_INCREMENT, edgeIndex.indexStruct.byteArraySize, blocksAdded, blocks, fromStore); await pushRecords(propIndex.indexStruct.startOffsets, propOffsetStart, propCount, OFFSET_INCREMENTS.PROP_INCREMENT, propIndex.indexStruct.byteArraySize, blocksAdded, blocks, fromStore); await pushValues(valueIndex.indexStruct.startOffsets, valueRefs, valueIndex.indexStruct.byteArraySize, blocksAdded, blocks, fromStore); return bufferSize; }; const packComputed = async (versionRoot, vertexOffsetStart, vertexCount, graphDepth, fromStore, chunk, valueCodec) => { const versionStore = await versionStoreFactory({ versionRoot, chunk, linkCodec, valueCodec, blockStore: fromStore, }); const graphStore = graphStoreFactory({ chunk, linkCodec, valueCodec, blockStore: fromStore, }); const graph = new Graph(versionStore, graphStore); return packFragment(graph, versionRoot, vertexOffsetStart, vertexCount, graphDepth, fromStore); }; const packFragment = async (graph, versionRoot, vertexOffsetStart, vertexCount, graphDepth, fromStore) => { let vertices = await graph.getVertexRange(vertexOffsetStart, vertexCount); const { buildRootIndex } = blockIndexFactory({ linkCodec, blockStore: fromStore, }); const { index } = await buildRootIndex(versionRoot); const { vertexRoot, edgeRoot, propRoot, valueRoot, indexRoot } = index; const blocksChecked = new Set(); const blocksAdded = new Set(); const blocks = new Set(); const roots = [vertexRoot, edgeRoot, propRoot, valueRoot, indexRoot]; const carRoots = [CID.asCID(versionRoot)]; let bufferSize = CarBufferWriter.headerLength({ roots: carRoots }); bufferSize += await computeSize(carRoots, fromStore); bufferSize += await computeSize(roots, fromStore); let edgeOffsetStart = 0; let edgeOffsetMax = 0; let propOffsetStart = 0; let propOffsetMax = 0; const targetVertices = new Set(); const targetEdges = new Set(); const targetProps = new Set(); for (const vertex of vertices) { const edges = await graph.getVertexEdges(vertex); for (const edge of edges) { targetEdges.add(edge); if (edge.offset < edgeOffsetStart || edgeOffsetStart === 0) { edgeOffsetStart = edge.offset; } if (edge.offset > edgeOffsetMax) { edgeOffsetMax = edge.offset; } const props = await graph.getEdgeProps(edge); for (const prop of props) { if (prop.offset < propOffsetStart || propOffsetStart === 0) { propOffsetStart = prop.offset; } if (prop.offset > propOffsetMax) { propOffsetMax = prop.offset; } } const targetVertex = await graph.getVertex(edge.target); targetVertices.add(targetVertex); } const props = await graph.getVertexProps(vertex); for (const prop of props) { targetProps.add(prop); if (prop.offset < propOffsetStart || propOffsetStart === 0) { propOffsetStart = prop.offset; } if (prop.offset > propOffsetMax) { propOffsetMax = prop.offset; } } } const edgeCount = (edgeOffsetMax - edgeOffsetStart) / OFFSET_INCREMENTS.EDGE_INCREMENT; const propCount = (propOffsetMax - propOffsetStart) / OFFSET_INCREMENTS.PROP_INCREMENT; bufferSize += await packRange(index, vertexOffsetStart, vertexCount, edgeOffsetStart, edgeCount, propOffsetStart, propCount, fromStore, blocksChecked, blocksAdded, blocks); let levelVertices = Array.from(targetVertices); let levelEdges = Array.from(targetEdges); let levelProps = Array.from(targetProps); for (let i = 1; i < graphDepth; i++) { bufferSize += await packRandom(index, levelVertices, levelEdges, levelProps, fromStore, blocksChecked, blocksAdded, blocks); targetVertices.clear(); targetEdges.clear(); targetProps.clear(); for (const vertex of levelVertices) { const edges = await graph.getVertexEdges(vertex); for (const edge of edges) { targetEdges.add(edge); const targetVertex = await graph.getVertex(edge.target); targetVertices.add(targetVertex); const props = await graph.getEdgeProps(edge); for (const prop of props) { targetProps.add(prop); } } const props = await graph.getVertexProps(vertex); for (const prop of props) { targetProps.add(prop); } } levelVertices = Array.from(targetVertices); levelEdges = Array.from(targetEdges); levelProps = Array.from(targetProps); } await pushMany(carRoots, fromStore, blocks); await pushMany(roots, fromStore, blocks); const buffer = new Uint8Array(bufferSize); const writer = CarBufferWriter.createWriter(buffer, { roots: carRoots, }); for (const block of blocks) { writer.write({ cid: CID.asCID(block.cid), bytes: block.bytes, }); } const bytes = writer.close(); const cid = await linkCodec.encode(bytes); return { cid, bytes }; }; const unpack = async (packBytes) => { const blockStore = memoryBlockStoreFactory(); return await restore(packBytes, blockStore); }; const restore = async (packBytes, intoStore) => { var _a, e_1, _b, _c; const reader = await CarReader.fromBytes(packBytes); const [root] = await reader.getRoots(); const blocks = []; try { for (var _d = true, _e = __asyncValues(reader.blocks()), _f; _f = await _e.next(), _a = _f.done, !_a;) { _c = _f.value; _d = false; try { const { cid, bytes } = _c; blocks.push({ cid, bytes }); await intoStore.put({ cid, bytes }); } finally { _d = true; } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_d && !_a && (_b = _e.return)) await _b.call(_e); } finally { if (e_1) throw e_1.error; } } const { buildRootIndex } = blockIndexFactory({ linkCodec, blockStore: intoStore, }); const { index } = await buildRootIndex(root); return { root, index, blocks }; }; const restoreGraphVersion = async (packBytes, intoStore) => { return await restore(packBytes, intoStore); }; const restoreSingleIndex = async (packBytes, intoStore) => { var _a, e_2, _b, _c; const reader = await CarReader.fromBytes(packBytes); const [root] = await reader.getRoots(); const blocks = []; try { for (var _d = true, _e = __asyncValues(reader.blocks()), _f; _f = await _e.next(), _a = _f.done, !_a;) { _c = _f.value; _d = false; try { const { cid, bytes } = _c; blocks.push({ cid, bytes }); await intoStore.put({ cid, bytes }); } finally { _d = true; } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (!_d && !_a && (_b = _e.return)) await _b.call(_e); } finally { if (e_2) throw e_2.error; } } const { buildChunkyIndex } = blockIndexFactory({ linkCodec, blockStore: intoStore, }); const { indexStruct } = await buildChunkyIndex(root); return { root, index: indexStruct, blocks }; }; const restoreRootIndex = async (packBytes, intoStore) => { var _a, e_3, _b, _c; const reader = await CarReader.fromBytes(packBytes); const [root] = await reader.getRoots(); const blocks = []; try { for (var _d = true, _e = __asyncValues(reader.blocks()), _f; _f = await _e.next(), _a = _f.done, !_a;) { _c = _f.value; _d = false; try { const { cid, bytes } = _c; blocks.push({ cid, bytes }); await intoStore.put({ cid, bytes }); } finally { _d = true; } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (!_d && !_a && (_b = _e.return)) await _b.call(_e); } finally { if (e_3) throw e_3.error; } } const { buildRootIndex } = blockIndexFactory({ linkCodec, blockStore: intoStore, }); const { index } = await buildRootIndex(root); return { root, index, blocks }; }; const packRandomBlocks = async (blocks) => { let bufferSize = 0; bufferSize += CarBufferWriter.headerLength({ roots: [] }); for (const block of blocks) { bufferSize += CarBufferWriter.blockLength({ cid: CID.asCID(block.cid), bytes: block.bytes, }); } const buffer = new Uint8Array(bufferSize); const writer = CarBufferWriter.createWriter(buffer); for (const block of blocks) { writer.write({ cid: CID.asCID(block.cid), bytes: block.bytes, }); } const bytes = writer.close(); const cid = await linkCodec.encode(bytes); return { cid, bytes }; }; const restoreRandomBlocks = async (packBytes, intoStore) => { var _a, e_4, _b, _c; const reader = await CarReader.fromBytes(packBytes); const blocks = []; try { for (var _d = true, _e = __asyncValues(reader.blocks()), _f; _f = await _e.next(), _a = _f.done, !_a;) { _c = _f.value; _d = false; try { const { cid, bytes } = _c; blocks.push({ cid, bytes }); await intoStore.put({ cid, bytes }); } finally { _d = true; } } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (!_d && !_a && (_b = _e.return)) await _b.call(_e); } finally { if (e_4) throw e_4.error; } } return blocks; }; const extractVersionBlocks = async ({ root, index }, fromStore) => { if (index === undefined) { const { buildRootIndex } = blockIndexFactory({ linkCodec, blockStore: fromStore, }); index = (await buildRootIndex(root)).index; } const { vertexIndex, edgeIndex, propIndex, valueIndex, indexIndex, vertexRoot, edgeRoot, propRoot, valueRoot, indexRoot, } = index; const indexes = [ vertexIndex, edgeIndex, propIndex, valueIndex, indexIndex, ]; const roots = [vertexRoot, edgeRoot, propRoot, valueRoot, indexRoot]; const blocks = new Set(); await pushMany([root], fromStore, blocks); await pushMany(roots, fromStore, blocks); for (const idx of indexes) { const { startOffsets } = idx.indexStruct; await pushMany(startOffsets.values(), fromStore, blocks); } return Array.from(blocks); }; return { extractVersionBlocks, packRandomBlocks, packVersionStore, packGraphVersion, packRootIndex, packGraph, packCommit, packComputed, packFragment, restore, restoreGraphVersion, restoreSingleIndex, restoreRootIndex, restoreRandomBlocks, unpack, }; }; export { graphPackerFactory }; //# sourceMappingURL=graph-packer.js.map