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

1,148 lines (1,093 loc) 36.2 kB
import { blockIndexFactory } from './block-index' import bounds from 'binary-search-bounds' import { BlockStore, memoryBlockStoreFactory } from './block-store' import { LinkCodec, ValueCodec } from './codecs' import { Block, Edge, Link, Prop, PropRecord, PropRef, RootIndex, ValueRef, Vertex, } from './types' import { CID } from 'multiformats/cid' import { CarReader, CarWriter, CarBufferWriter } from '@ipld/car' import { Writer } from '@ipld/car/dist/src/buffer-writer' import { VersionStore, 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' type Slice = { startOffset: number length: number } const { read } = chunkyStore() /** * The GraphPacker is responsible for packing and unpacking complete graph versions or fragments thereof in a single large block. */ interface GraphPacker { /** * Extract all blocks that make up a complete graph version. */ extractVersionBlocks: ( { root, index }: { root: Link; index?: RootIndex }, fromStore: BlockStore ) => Promise<Block[]> /** * Packs a complete graph story into a single block. */ packVersionStore: ( versionStoreRoot: Link, fromStore: BlockStore, chunk: (buffer: Uint8Array) => Uint32Array, valueCodec: ValueCodec ) => Promise<Block> /** * Packs a complete graph version into a single block. * @deprecated */ packGraph: (versionRoot: Link, fromStore: BlockStore) => Promise<Block> /** * Packs a complete graph version into a single block. */ packGraphVersion: ( versionRoot: Link, fromStore: BlockStore ) => Promise<Block> /** * Packs a complete root index into a single block. */ packRootIndex: (versionRoot: Link, fromStore: BlockStore) => Promise<Block> /** * Packs the commit results into a single block. */ packCommit: ({ root, index, blocks, }: { root: Link index: RootIndex blocks: Block[] }) => Promise<Block> /** * Packs a vertex data slice together with the derived edge and property data into a single block. */ packComputed: ( versionRoot: Link, vertexOffsetStart: number, vertexCount: number, graphDepth: number, fromStore: BlockStore, chunk: (buffer: Uint8Array) => Uint32Array, valueCodec: ValueCodec ) => Promise<Block> /** * Packs a vertex data slice together with the derived edge and property data into a single block. Graph API friendly. */ packFragment: ( graph: Graph, versionRoot: Link, vertexOffsetStart: number, vertexCount: number, graphDepth: number, fromStore: BlockStore ) => Promise<Block> /** * Unpacks and restores packed graph data into a block store. * @deprecated */ restore: ( pack: Uint8Array, intoStore: BlockStore ) => Promise<{ root: Link; index: RootIndex; blocks: Block[] }> /** * Restores a packed graph version into a block store. * @see packGraphVersion */ restoreGraphVersion: ( pack: Uint8Array, intoStore: BlockStore ) => Promise<{ root: Link; index: RootIndex; blocks: Block[] }> /** * Unpacks and restores single index data into a block store. */ restoreSingleIndex: ( packBytes: Uint8Array, intoStore: BlockStore ) => Promise<{ root: Link index: { startOffsets: Map<number, any> indexSize: number byteArraySize: number } blocks: Block[] }> restoreRootIndex: ( packBytes: Uint8Array, intoStore: BlockStore ) => Promise<{ root: Link index: RootIndex blocks: Block[] }> /** * Unpacks graph packed data */ unpack: ( pack: Uint8Array ) => Promise<{ root: Link; index: RootIndex; blocks: Block[] }> packRandomBlocks: (blocks: Block[]) => Promise<Block> restoreRandomBlocks: ( pack: Uint8Array, intoStore: BlockStore ) => Promise<Block[]> } const graphPackerFactory = (linkCodec: LinkCodec): GraphPacker => { const computeSize = async ( cids: Iterable<any>, fromStore: BlockStore ): Promise<number> => { 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: Iterable<any>, fromStore: BlockStore, writer: Writer ): Promise<void> => { for (const cid of cids) { const bytes = await fromStore.get(cid) writer.write({ cid, bytes }) } } const pushMany = async ( cids: Iterable<any>, fromStore: BlockStore, blocks: Set<Block> ): Promise<void> => { for (const cid of cids) { const bytes = await fromStore.get(cid) blocks.add({ cid, bytes }) } } const recordOffsets = ( firstOffset: number, recordCount: number, recordIncrement: number ) => { const offsets = [] for (let i = 0; i < recordCount; i++) { offsets.push(firstOffset + i * recordIncrement) } return offsets } const readPropValueRefs = async ( propOffsetStart: number, propCount: number, index: RootIndex, fromStore: BlockStore ): Promise<ValueRef[]> => { 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: PropRecord[] = await new PropRecordDecoder( bytes ).read() return propRecords.map((propRecord) => propRecord.valueRef) } const findSlices = (valueRefs: ValueRef[]): Slice[] => { if (valueRefs.length === 0) return [] let startOffset = valueRefs[0].ref let endOffset = startOffset + valueRefs[0].length const ranges: number[][] = [] 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: Map<number, CID>, valueRefs: ValueRef[], valueArraySize: number ): Promise<CID[]> => { const slices = findSlices(valueRefs) const result: CID[] = [] for (const slice of slices) { const blocks: CID[] = await identifyBlocks( startOffsets, slice, valueArraySize ) result.push(...blocks) } return result } const relevantChunks = ( startOffsetArray: any[], startOffset: number, endOffset: number, pad: number ): any[] => { return startOffsetArray.slice( bounds.le(startOffsetArray, startOffset), bounds.ge(startOffsetArray, endOffset) + pad ) } const identifyBlocks = async ( startOffsets: Map<number, CID>, slice: Slice, byteArraySize: number ): Promise<CID[]> => { 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: Map<number, CID>, recordOffset: number, recordCount: number, recordSize: number, recordArraySize: number, blocksChecked: Set<string>, fromStore: BlockStore ): Promise<number> => { 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: Map<number, CID>, valueRefs: ValueRef[], valueArraySize: number, blocksChecked: Set<string>, fromStore: BlockStore ): Promise<number> => { 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: Map<number, CID>, recordOffset: number, recordCount: number, recordSize: number, recordArraySize: number, blocksAdded: Set<string>, blocks: Set<Block>, fromStore: BlockStore ) => { 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: Map<number, CID>, valueRefs: ValueRef[], valueArraySize: number, blocksAdded: Set<string>, blocks: Set<Block>, fromStore: BlockStore ) => { 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, }: { root: Link index: RootIndex blocks: Block[] }): Promise<Block> => { 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: 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: Link, fromStore: BlockStore, chunk: (buffer: Uint8Array) => Uint32Array, valueCodec: ValueCodec ): Promise<Block> => { const versionStore: 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: 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: Link, fromStore: BlockStore ): Promise<Block> => { return await packGraph(versionRoot, fromStore) } const packGraph = async ( versionRoot: Link, fromStore: BlockStore ): Promise<Block> => { 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: 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: Link, fromStore: BlockStore ): Promise<Block> => { 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: 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: RootIndex, vertices: Vertex[], edges: Edge[], props: Prop[], fromStore: BlockStore, blocksChecked: Set<string>, blocksAdded: Set<string>, blocks: Set<Block> ): Promise<number> => { 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: RootIndex, vertexOffsetStart: number, vertexCount: number, edgeOffsetStart: number, edgeCount: number, propOffsetStart: number, propCount: number, fromStore: BlockStore, blocksChecked: Set<string>, blocksAdded: Set<string>, blocks: Set<Block> ): Promise<number> => { 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: Link, vertexOffsetStart: number, vertexCount: number, graphDepth: number, fromStore: BlockStore, chunk: (buffer: Uint8Array) => Uint32Array, valueCodec: ValueCodec ): Promise<Block> => { const versionStore: 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: Graph, versionRoot: Link, vertexOffsetStart: number, vertexCount: number, graphDepth: number, fromStore: BlockStore ): Promise<Block> => { let vertices: Vertex[] = 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<string>() const blocksAdded = new Set<string>() const blocks = new Set<Block>() 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<Vertex>() const targetEdges = new Set<Edge>() const targetProps = new Set<Prop>() 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: Vertex = 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: Vertex[] = Array.from(targetVertices) let levelEdges: Edge[] = Array.from(targetEdges) let levelProps: Prop[] = 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: Vertex = 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: 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: Uint8Array ): Promise<{ root: Link; index: RootIndex; blocks: Block[] }> => { const blockStore: BlockStore = memoryBlockStoreFactory() return await restore(packBytes, blockStore) } const restore = async ( packBytes: Uint8Array, intoStore: BlockStore ): Promise<{ root: Link; index: RootIndex; blocks: Block[] }> => { const reader = await CarReader.fromBytes(packBytes) const [root] = await reader.getRoots() const blocks: Block[] = [] for await (const { cid, bytes } of reader.blocks()) { blocks.push({ cid, bytes }) await intoStore.put({ cid, bytes }) } const { buildRootIndex } = blockIndexFactory({ linkCodec, blockStore: intoStore, }) const { index } = await buildRootIndex(root) return { root, index, blocks } } const restoreGraphVersion = async ( packBytes: Uint8Array, intoStore: BlockStore ): Promise<{ root: Link index: RootIndex blocks: Block[] }> => { return await restore(packBytes, intoStore) } const restoreSingleIndex = async ( packBytes: Uint8Array, intoStore: BlockStore ): Promise<{ root: Link index: { startOffsets: Map<number, any> indexSize: number byteArraySize: number } blocks: Block[] }> => { const reader = await CarReader.fromBytes(packBytes) const [root] = await reader.getRoots() const blocks: Block[] = [] for await (const { cid, bytes } of reader.blocks()) { blocks.push({ cid, bytes }) await intoStore.put({ cid, bytes }) } const { buildChunkyIndex } = blockIndexFactory({ linkCodec, blockStore: intoStore, }) const { indexStruct } = await buildChunkyIndex(root) return { root, index: indexStruct, blocks } } const restoreRootIndex = async ( packBytes: Uint8Array, intoStore: BlockStore ): Promise<{ root: Link index: RootIndex blocks: Block[] }> => { const reader = await CarReader.fromBytes(packBytes) const [root] = await reader.getRoots() const blocks: Block[] = [] for await (const { cid, bytes } of reader.blocks()) { blocks.push({ cid, bytes }) await intoStore.put({ cid, bytes }) } const { buildRootIndex } = blockIndexFactory({ linkCodec, blockStore: intoStore, }) const { index } = await buildRootIndex(root) return { root, index, blocks } } const packRandomBlocks = async (blocks: Block[]): Promise<Block> => { 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: 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: Uint8Array, intoStore: BlockStore ): Promise<Block[]> => { const reader = await CarReader.fromBytes(packBytes) const blocks: Block[] = [] for await (const { cid, bytes } of reader.blocks()) { blocks.push({ cid, bytes }) await intoStore.put({ cid, bytes }) } return blocks } const extractVersionBlocks = async ( { root, index }: { root: Link; index?: RootIndex }, fromStore: BlockStore ): Promise<Block[]> => { 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<Block>() 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, GraphPacker }