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

241 lines 9.28 kB
import { chunkyStore } from '@dstanesc/store-chunky-bytes'; import { VersionDecoder, VersionEncoder } from './serde'; import { v4 as uuidV4, parse as uuidParse } from 'uuid'; import { blockIndexFactory } from './block-index'; import { MergePolicyEnum, merge } from './merge'; import { graphPackerFactory } from './graph-packer'; const { create, readAll } = chunkyStore(); const VERSION_UNDEFINED = { version: undefined, index: undefined }; const ROOT_UNDEFINED = { root: undefined, index: undefined }; const versionStoreFactory = async ({ readOnly = false, storeRoot, versionRoot, chunk, linkCodec, valueCodec, blockStore, }) => { const versions = new Map(); const indices = new Map(); let identity; let byteArrayRoot; let currentVersion; const { buildRootIndex } = blockIndexFactory({ linkCodec, blockStore, }); const versionStoreRoot = () => byteArrayRoot; const id = () => identity.toString(); const log = () => { const versionArray = Array.from(versions.values()); return versionArray.reverse(); }; const init = async (storeRoot) => { if (storeRoot !== undefined) { const bytes = await readAll({ root: storeRoot, decode: linkCodec.decode, get: blockStore.get, }); const { id: storeId, versions: versionArray } = new VersionDecoder(bytes, linkCodec.decode, valueCodec.decode).read(); byteArrayRoot = storeRoot; versionArray.forEach((v) => versions.set(v.root.toString(), v)); identity = storeId; currentVersion = versionArray[versionArray.length - 1].root; } else { const bytes = uuidParse(uuidV4()); const buffer = new Uint8Array(16); buffer.set(bytes, 0); identity = await linkCodec.encode(buffer); } }; const versionSet = async ({ version, index, }) => { if (index === undefined) { const { index: indexBuilt } = await buildRootIndex(version.root); index = indexBuilt; } versions.set(version.root.toString(), version); indices.set(version.root.toString(), index); currentVersion = version.root; if (!readOnly) { const { root, blocks } = await blocksExtract(); byteArrayRoot = root; for (const block of blocks) { await blockStore.put(block); } return root; } else return undefined; }; const blocksExtract = async () => { const buf = new VersionEncoder(identity, Array.from(versions.values()), valueCodec.encode).write(); const { root, index, blocks } = await create({ buf, chunk, encode: linkCodec.encode, }); return { root, index, blocks }; }; const versionGet = async () => { if (currentVersion !== undefined) { const version = versions.get(currentVersion.toString()); const index = indices.get(currentVersion.toString()); if (index !== undefined) return { version, index }; else { const { index } = await buildRootIndex(version.root); return { version, index }; } } else return VERSION_UNDEFINED; }; const rootSet = async ({ root, index, parent, mergeParent, }) => { const existingVersion = versions.get(root.toString()); if (existingVersion !== undefined) { currentVersion = existingVersion.root; return existingVersion.root; } else { const details = { timestamp: Date.now() }; const version = { root, details }; if (parent !== undefined) { version.parent = parent; } if (mergeParent !== undefined) { version.mergeParent = mergeParent; } return await versionSet({ version, index }); } }; const rootGet = async () => { const { version, index } = await versionGet(); return version === undefined ? ROOT_UNDEFINED : { root: version.root, index }; }; const checkout = (root) => { if (versions.has(root.toString())) { currentVersion = root; } else throw new Error(`Unknown version ${root.toString()}`); }; const diff = (other) => { if (other.id() !== id()) throw new Error(`Cannot compare version stores with different identities ${other.id()} !== ${id()}`); const otherLog = other.log(); let missing = []; let common = []; for (const version of otherLog) { if (versions.has(version.root.toString())) { common.push(version); } else { missing.push(version); } } return { last: common.length > 0 ? common[0] : undefined, common, missing, }; }; const loadBlocks = async (cids, otherBlockStore) => { const out = []; for (const cid of cids) { out.push({ cid, bytes: await otherBlockStore.get(cid) }); } return out; }; const findMissingBlocks = async (other, otherBlockStore) => { const { last, common, missing } = diff(other); const out = new Set(); const { buildRootIndex } = blockIndexFactory({ linkCodec, blockStore: otherBlockStore, }); for (const version of missing) { out.add(version.root); const { index: rootIndex } = await buildRootIndex(version.root); out.add(rootIndex.vertexRoot); out.add(rootIndex.edgeRoot); out.add(rootIndex.propRoot); out.add(rootIndex.valueRoot); out.add(rootIndex.indexRoot); for (const cid of rootIndex.vertexIndex.indexStruct.startOffsets.values()) { out.add(cid); } for (const cid of rootIndex.edgeIndex.indexStruct.startOffsets.values()) { out.add(cid); } for (const cid of rootIndex.propIndex.indexStruct.startOffsets.values()) { out.add(cid); } for (const cid of rootIndex.valueIndex.indexStruct.startOffsets.values()) { out.add(cid); } for (const cid of rootIndex.indexIndex.indexStruct.startOffsets.values()) { out.add(cid); } } return out; }; const packMissingBlocks = async (other, otherBlockStore) => { const { packRandomBlocks } = graphPackerFactory(linkCodec); const missing = await findMissingBlocks(other, otherBlockStore); const blocks = await loadBlocks(missing, otherBlockStore); const bundle = await packRandomBlocks(blocks); return bundle; }; const mergeVersions = async (other) => { const { last, common, missing } = diff(other); if (missing.length > 0) { const first = currentVersion; const second = other.currentRoot(); const { version: otherVersion, index: otherIndex } = await other.versionGet(); if (otherVersion.parent !== undefined && otherVersion.parent.toString() === first.toString()) { // fast forward versionSet({ version: otherVersion, index: otherIndex }); const { extractVersionBlocks } = graphPackerFactory(linkCodec); const otherBlocks = await extractVersionBlocks({ root: second, index: otherIndex }, blockStore); return { root: second, index: otherIndex, blocks: otherBlocks }; } else { const { root, index, blocks } = await merge({ baseRoot: last.root, baseStore: blockStore, currentRoot: first, currentStore: blockStore, otherRoot: second, otherStore: blockStore, }, MergePolicyEnum.MultiValueRegistry, chunk, linkCodec, valueCodec); rootSet({ root, index, parent: first, mergeParent: second }); return { root, index, blocks }; } } else { const { version, index } = await versionGet(); const { extractVersionBlocks } = graphPackerFactory(linkCodec); const blocks = await extractVersionBlocks({ root: version.root, index }, blockStore); return { root: version.root, index, blocks }; } }; await init(storeRoot); if (versionRoot !== undefined) { await rootSet({ root: versionRoot }); } return { id, currentRoot: () => currentVersion, versionStoreRoot, versionSet, versionGet, rootSet, rootGet, checkout, log, blocksExtract, diff, packMissingBlocks, mergeVersions, }; }; export { versionStoreFactory }; //# sourceMappingURL=version-store.js.map