@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
JavaScript
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