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

625 lines (566 loc) 17.6 kB
import { BlockStore } from './block-store' import { fastCloneEdge, fastCloneProp, fastCloneVertex } from './clone' import { Link, Part, Vertex, Edge, Prop, RootIndex, Block } from './types' import { BaselineDelta, deltaFactory, DeltaFactory } from './delta' import { Graph, Tx } from './graph' import { blockIndexFactory } from './block-index' import { BlockCodec, LinkCodec, ValueCodec } from './codecs' import { graphStore } from './graph-store' import { OFFSET_INCREMENTS } from './serde' import { VersionStore, versionStoreFactory } from './version-store' enum MergePolicyEnum { LastWriterWins, MultiValueRegistry, } interface MergeOrder { ({ current, other }: { current: any; other: any }): { first: any second: any } } interface MergePolicy { mergeVertex: ( tx: Tx, baseline: Vertex, first: Vertex, second: Vertex ) => Promise<Vertex> mergeEdge: ( tx: Tx, baseline: Edge, first: Edge, second: Edge ) => Promise<Edge> mergeProp: ( tx: Tx, baseline: Prop, first: Prop, second: Prop ) => Promise<Prop> } // account on the fact that only crdt operations will be supported on the graph const mergePolicyLastWriterWins = (): MergePolicy => { const mergeVertex = async ( tx: Tx, baseline: Vertex, first: Vertex, second: Vertex ): Promise<Vertex> => { const target: Vertex = fastCloneVertex(first) if (second.nextEdge !== baseline.nextEdge) target.nextEdge = second.nextEdge if (second.nextProp !== baseline.nextProp) target.nextProp = second.nextProp return target } const mergeEdge = async ( tx: Tx, baseline: Edge, first: Edge, second: Edge ): Promise<Edge> => { const target: Edge = fastCloneEdge(first) if (second.sourceNext !== baseline.sourceNext) target.sourceNext = second.sourceNext if (second.nextProp !== baseline.nextProp) target.nextProp = second.nextProp return target } const mergeProp = async ( tx: Tx, baseline: Prop, first: Prop, second: Prop ): Promise<Prop> => { const target: Prop = fastCloneProp(first) if (second.nextProp !== baseline.nextProp) target.nextProp = second.nextProp return target } return { mergeVertex, mergeEdge, mergeProp } } const mergePolicyMultiValueRegistry = (): MergePolicy => { const mergeVertex = async ( tx: Tx, baseline: Vertex, first: Vertex, second: Vertex ): Promise<Vertex> => { const target: Vertex = fastCloneVertex(first) if (second.nextEdge !== baseline.nextEdge) await tx.linkVertexEdge(target, second.nextEdge) if (second.nextProp !== baseline.nextProp) await tx.linkVertexProp(target, second.nextProp) return target } const mergeEdge = async ( tx: Tx, baseline: Edge, first: Edge, second: Edge ): Promise<Edge> => { const target: Edge = fastCloneEdge(first) if ( second.sourceNext !== undefined && second.sourceNext !== baseline.sourceNext ) await tx.linkEdge(target, second.sourceNext) if (second.nextProp !== baseline.nextProp) await tx.linkEdgeProp(target, second.nextProp) return target } const mergeProp = async ( tx: Tx, baseline: Prop, first: Prop, second: Prop ): Promise<Prop> => { const target: Prop = fastCloneProp(first) if (second.nextProp !== baseline.nextProp) await tx.linkProp(target, second.nextProp) return target } return { mergeVertex, mergeEdge, mergeProp } } const merge = async ( { baseRoot, baseStore, currentRoot, currentStore, otherRoot, otherStore, }: { baseRoot: Link baseStore: BlockStore currentRoot: Link currentStore: BlockStore otherRoot: Link otherStore: BlockStore }, policy: MergePolicyEnum, chunk: (buffer: Uint8Array) => Uint32Array, linkCodec: LinkCodec, valueCodec: ValueCodec ): Promise<{ root: Link; index: RootIndex; blocks: Block[] }> => { const mergeOrder = <T>({ current, other }: { current: T; other: T }) => { return currentRoot.bytes[0] < otherRoot.bytes[0] ? { first: current, second: other } : { first: other, second: current } } let mergePolicy: MergePolicy switch (policy) { case MergePolicyEnum.LastWriterWins: mergePolicy = mergePolicyLastWriterWins() break case MergePolicyEnum.MultiValueRegistry: mergePolicy = mergePolicyMultiValueRegistry() break default: throw new Error(`Unknown merge policy ${policy}`) } const { baselineDelta } = deltaFactory({ linkCodec, valueCodec }) const { buildRootIndex: buildBaseIndex } = blockIndexFactory({ linkCodec, blockStore: baseStore, }) const { buildRootIndex: buildCurrentIndex } = blockIndexFactory({ linkCodec, blockStore: currentStore, }) const { buildRootIndex: buildOtherIndex } = blockIndexFactory({ linkCodec, blockStore: otherStore, }) const { root: baseRootRef, index: baseIndex } = await buildBaseIndex( baseRoot ) const { root: currentRootRef, index: currentIndex } = await buildCurrentIndex(currentRoot) const { root: otherRootRef, index: otherIndex } = await buildOtherIndex( otherRoot ) const { first: { root: firstRoot, index: firstIndex, blockStore: firstStore }, second, } = mergeOrder({ current: { root: currentRootRef, index: currentIndex, blockStore: currentStore, }, other: { root: otherRootRef, index: otherIndex, blockStore: otherStore, }, }) const story: VersionStore = await versionStoreFactory({ chunk, linkCodec, valueCodec, blockStore: firstStore, }) const storeRoot = await story.rootSet({ root: firstRoot, index: firstIndex, }) const store = graphStore({ chunk, linkCodec, valueCodec, blockStore: firstStore, }) const graph = new Graph(story, store) const tx: Tx = graph.tx() await tx.start() await policyMerge(tx, mergeOrder, mergePolicy, baselineDelta, { baseRoot, baseIndex, baseStore, currentRoot, currentIndex, currentStore, otherRoot, otherIndex, otherStore, }) return await tx.commit({}) } const policyMerge = async ( tx: Tx, mergeOrder: MergeOrder, mergePolicy: MergePolicy, baselineDelta: BaselineDelta, { baseRoot, baseIndex, baseStore, currentRoot, currentIndex, currentStore, otherRoot, otherIndex, otherStore, }: { baseRoot: Link baseIndex: RootIndex baseStore: BlockStore currentRoot: Link currentIndex: RootIndex currentStore: BlockStore otherRoot: Link otherIndex: RootIndex otherStore: BlockStore } ) => { const { vertexIndex, edgeIndex, propIndex } = baseIndex const { byteArraySize: vertexRebaseThreshold } = vertexIndex.indexStruct const { byteArraySize: edgeRebaseThreshold } = edgeIndex.indexStruct const { byteArraySize: propRebaseThreshold } = propIndex.indexStruct const currentDelta = await baselineDelta({ baseRoot, baseIndex, baseStore, currentRoot, currentIndex, currentStore, }) const otherDelta = await baselineDelta({ baseRoot, baseIndex, baseStore, currentRoot: otherRoot, currentIndex: otherIndex, currentStore: otherStore, }) const { first: verticesFirst, second: verticesSecond } = mergeOrder({ current: currentDelta.vertices.added, other: otherDelta.vertices.added, }) const { rebased: verticesRebased, rebaseOffsetDelta: vertexRebaseOffsetDelta, } = await rebaseAdditionOffsets( { first: verticesFirst, second: verticesSecond }, fastCloneVertex, OFFSET_INCREMENTS.VERTEX_INCREMENT ) const { first: edgesFirst, second: edgesSecond } = mergeOrder({ current: currentDelta.edges.added, other: otherDelta.edges.added, }) const { rebased: edgesRebased, rebaseOffsetDelta: edgeRebaseOffsetDelta } = await rebaseAdditionOffsets( { first: edgesFirst, second: edgesSecond }, fastCloneEdge, OFFSET_INCREMENTS.EDGE_INCREMENT ) const { first: propsFirst, second: propsSecond } = mergeOrder({ current: currentDelta.props.added, other: otherDelta.props.added, }) const { rebased: propsRebased, rebaseOffsetDelta: propRebaseOffsetDelta } = await rebaseAdditionOffsets( { first: propsFirst, second: propsSecond }, fastCloneProp, OFFSET_INCREMENTS.PROP_INCREMENT ) await rebaseVertexEdgeRef({ verticesRebased, edgeRebaseThreshold, edgeRebaseOffsetDelta, }) await rebaseVertexPropRef({ verticesRebased, propRebaseThreshold, propRebaseOffsetDelta, }) await rebaseEdgeVertexRef({ edgesRebased, vertexRebaseThreshold, vertexRebaseOffsetDelta, }) await rebaseEdgeEdgeRef({ edgesRebased, edgeRebaseThreshold, edgeRebaseOffsetDelta, }) await rebaseEdgePropRef({ edgesRebased, propRebaseThreshold, propRebaseOffsetDelta, }) await rebasePropPropRef({ propsRebased, propRebaseThreshold, propRebaseOffsetDelta, }) const { first: verticesFirstUpdated, second: verticesSecondUpdated } = mergeOrder({ current: currentDelta.vertices.updated, other: otherDelta.vertices.updated, }) await rebaseVertexEdgeRef({ verticesRebased: verticesSecondUpdated, edgeRebaseThreshold, edgeRebaseOffsetDelta, }) await rebaseVertexPropRef({ verticesRebased: verticesSecondUpdated, propRebaseThreshold, propRebaseOffsetDelta, }) const { first: edgesFirstUpdated, second: edgesSecondUpdated } = mergeOrder( { current: currentDelta.edges.updated, other: otherDelta.edges.updated } ) await rebaseEdgeVertexRef({ edgesRebased: edgesSecondUpdated, vertexRebaseThreshold, vertexRebaseOffsetDelta, }) await rebaseEdgeEdgeRef({ edgesRebased: edgesSecondUpdated, edgeRebaseThreshold, edgeRebaseOffsetDelta, }) await rebaseEdgePropRef({ edgesRebased: edgesSecondUpdated, propRebaseThreshold, propRebaseOffsetDelta, }) const { first: propsFirstUpdated, second: propsSecondUpdated } = mergeOrder( { current: currentDelta.props.updated, other: otherDelta.props.updated } ) await rebasePropPropRef({ propsRebased: propsSecondUpdated, propRebaseThreshold, propRebaseOffsetDelta, }) for (const [vertexOffset, vertex] of verticesRebased) tx.vertices.added.set(vertexOffset, vertex) for (const [edgeOffset, edge] of edgesRebased) tx.edges.added.set(edgeOffset, edge) for (const [propOffset, prop] of propsRebased) tx.props.added.set(propOffset, prop) const verticesUpdated = await applyUpdates( tx, { first: verticesFirstUpdated, second: verticesSecondUpdated, baseline: currentDelta.vertices.updateBaseline, }, mergePolicy.mergeVertex ) const edgesUpdated = await applyUpdates( tx, { first: edgesFirstUpdated, second: edgesSecondUpdated, baseline: currentDelta.edges.updateBaseline, }, mergePolicy.mergeEdge ) const propsUpdated = await applyUpdates( tx, { first: propsFirstUpdated, second: propsSecondUpdated, baseline: currentDelta.props.updateBaseline, }, mergePolicy.mergeProp ) for (const [vertexOffset, vertex] of verticesUpdated) tx.vertices.updated.set(vertexOffset, vertex) for (const [edgeOffset, edge] of edgesUpdated) tx.edges.updated.set(edgeOffset, edge) for (const [propOffset, prop] of propsUpdated) tx.props.updated.set(propOffset, prop) } const rebaseAdditionOffsets = async <T extends Part>( { first, second }: { first: Map<number, T>; second: Map<number, T> }, fastClone: (part: T) => T, recordSize: number ) => { let rebased: Map<number, T> = new Map() let rebaseOffsetDelta = 0 if (first.size > 0 && second.size > 0) { const firstAddedArray: number[] = Array.from(first.keys()) const firstOffset = firstAddedArray[0] const lastOffset = firstAddedArray[first.size - 1] rebaseOffsetDelta = lastOffset - firstOffset + recordSize for (const secondPart of second.values()) { const secondPartClone = fastClone(secondPart) secondPartClone.offset += rebaseOffsetDelta rebased.set(secondPartClone.offset, secondPartClone) } } return { rebased, rebaseOffsetDelta } } const rebaseVertexEdgeRef = async ({ verticesRebased, edgeRebaseThreshold, edgeRebaseOffsetDelta, }: { verticesRebased: Map<number, Vertex> edgeRebaseThreshold: number edgeRebaseOffsetDelta: number }) => { for (const vertex of verticesRebased.values()) { if (vertex.nextEdge >= edgeRebaseThreshold) vertex.nextEdge += edgeRebaseOffsetDelta } } const rebaseVertexPropRef = async ({ verticesRebased, propRebaseThreshold, propRebaseOffsetDelta, }: { verticesRebased: Map<number, Vertex> propRebaseThreshold: number propRebaseOffsetDelta: number }) => { for (const vertex of verticesRebased.values()) { if (vertex.nextProp >= propRebaseThreshold) vertex.nextProp += propRebaseOffsetDelta } } const rebaseEdgeVertexRef = async ({ edgesRebased, vertexRebaseThreshold, vertexRebaseOffsetDelta, }: { edgesRebased: Map<number, Edge> vertexRebaseThreshold: number vertexRebaseOffsetDelta: number }) => { for (const edge of edgesRebased.values()) { if (edge.source >= vertexRebaseThreshold) edge.source += vertexRebaseOffsetDelta if (edge.target >= vertexRebaseThreshold) edge.target += vertexRebaseOffsetDelta } } const rebaseEdgeEdgeRef = async ({ edgesRebased, edgeRebaseThreshold, edgeRebaseOffsetDelta, }: { edgesRebased: Map<number, Edge> edgeRebaseThreshold: number edgeRebaseOffsetDelta: number }) => { for (const edge of edgesRebased.values()) { if (edge.sourcePrev >= edgeRebaseThreshold) edge.sourcePrev += edgeRebaseOffsetDelta if (edge.sourceNext >= edgeRebaseThreshold) edge.sourceNext += edgeRebaseOffsetDelta } } const rebaseEdgePropRef = async ({ edgesRebased, propRebaseThreshold, propRebaseOffsetDelta, }: { edgesRebased: Map<number, Edge> propRebaseThreshold: number propRebaseOffsetDelta: number }) => { for (const edge of edgesRebased.values()) { if (edge.nextProp >= propRebaseThreshold) edge.nextProp += propRebaseOffsetDelta } } const rebasePropPropRef = async ({ propsRebased, propRebaseThreshold, propRebaseOffsetDelta, }: { propsRebased: Map<number, Prop> propRebaseThreshold: number propRebaseOffsetDelta: number }) => { for (const prop of propsRebased.values()) { if (prop.nextProp >= propRebaseThreshold) prop.nextProp += propRebaseOffsetDelta } } const applyUpdates = async <T extends Part>( tx: Tx, { first, second, baseline, }: { first: Map<number, T> second: Map<number, T> baseline: Map<number, T> }, merge: (tx: Tx, baseline: T, first: Part, second: T) => Promise<T> ) => { let updated: Map<number, T> if (first.size > 0 && second.size > 0) { updated = new Map(first) for (const secondPart of second.values()) { if (first.has(secondPart.offset)) { const baselineVertex = baseline.get(secondPart.offset) const firstVertex = first.get(secondPart.offset) const merged = await merge( tx, baselineVertex, firstVertex, secondPart ) updated.set(secondPart.offset, merged) } else { updated.set(secondPart.offset, secondPart) } } } else if (first.size > 0) { updated = new Map(first) } else { updated = new Map(second) } return updated } export { merge, MergePolicyEnum }