@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,035 lines (959 loc) • 32.7 kB
text/typescript
import {
Vertex,
Edge,
Prop,
Offset,
VertexRef,
EdgeRef,
PropRef,
VertexType,
EdgeType,
PropType,
KeyType,
PropValue,
Link,
Block,
RootIndex,
IndexedValue,
Index,
IndexRef,
IndexType,
Status,
Part,
Version,
Comment,
Tag,
VersionDetails,
} from './types'
import { OFFSET_INCREMENTS } from './serde'
import { TraversalVisitor, traverseVertices } from './depth-first'
import { IndexStore } from './index-store'
import { PropPredicate } from './navigate'
import { Signer } from './trust'
interface ElementAccessor {
getVertex: (ref: VertexRef) => Promise<Vertex>
getEdge: (ref: EdgeRef) => Promise<Edge>
getProp: (ref: PropRef) => Promise<Prop>
}
async function* edgesOutgoing(
vertex: Vertex,
accessor: ElementAccessor
): AsyncGenerator<Edge, void, void> {
yield* edgeSourceNext(vertex.nextEdge, accessor)
}
async function* edgeSourceNext(
ref: EdgeRef,
accessor: ElementAccessor
): AsyncGenerator<Edge, void, void> {
if (ref !== undefined) {
const edge = await accessor.getEdge(ref)
yield edge
yield* edgeSourceNext(edge.sourceNext, accessor)
}
}
async function* propsNext(
ref: PropRef,
accessor: ElementAccessor
): AsyncGenerator<Prop, void, void> {
if (ref !== undefined) {
const prop = await accessor.getProp(ref)
yield prop
yield* propsNext(prop.nextProp, accessor)
}
}
function incomingEdgesVisitor(
incomingEdges: Map<VertexRef, Set<EdgeRef>>
): TraversalVisitor {
const endEdge = async (edge: Edge) => {
const edgeOffset = edge.offset
const targetOffset = edge.target
if (incomingEdges.has(targetOffset)) {
const edgeSet = incomingEdges.get(targetOffset)
edgeSet.add(edgeOffset)
} else {
const edgeSet = new Set<EdgeRef>()
edgeSet.add(edgeOffset)
incomingEdges.set(targetOffset, edgeSet)
}
}
return { endEdge }
}
class Graph implements ElementAccessor {
vertices: Map<number, Vertex>
edges: Map<number, Edge>
props: Map<number, Prop>
indices: Map<number, Index>
versionSet: ({
version,
index,
}: {
version: Version
index: RootIndex
}) => Promise<Link>
rootGet: () => Promise<{ root: Link; index: RootIndex }>
vertexGet: (
{ root, index }: { root: Link; index: RootIndex },
offset: number
) => Promise<Vertex>
vertexRange: (
{ root, index }: { root: Link; index: RootIndex },
offset: number,
count: number
) => Promise<Vertex[]>
edgeGet: (
{ root, index }: { root: Link; index: RootIndex },
offset: number
) => Promise<Edge>
edgeRange: (
{ root, index }: { root: Link; index: RootIndex },
offset: number,
count: number
) => Promise<Edge[]>
propGet: (
{ root, index }: { root: Link; index: RootIndex },
offset: number
) => Promise<Prop>
propRange: (
{ root, index }: { root: Link; index: RootIndex },
offset: number,
count: number
) => Promise<Prop[]>
indexGet: (
{ root, index }: { root: Link; index: RootIndex },
offset: number
) => Promise<Index>
offsetsGet: ({ root, index }: { root: Link; index: RootIndex }) => Promise<{
vertexOffset: Offset
edgeOffset: Offset
propOffset: Offset
indexOffset: Offset
}>
verticesAll: ({
root,
index,
}: {
root: Link
index: RootIndex
}) => Promise<Vertex[]>
edgesAll: ({
root,
index,
}: {
root: Link
index: RootIndex
}) => Promise<Edge[]>
propsAll: ({
root,
index,
}: {
root: Link
index: RootIndex
}) => Promise<Prop[]>
commit: (
{ root, index }: { root: Link; index: RootIndex },
{
vertices,
edges,
props,
}: {
vertices: {
added: Map<number, Vertex>
updated: Map<number, Vertex>
}
edges: { added: Map<number, Edge>; updated: Map<number, Edge> }
props: { added: Map<number, Prop>; updated: Map<number, Prop> }
indices: { added: Map<number, Index>; updated: Map<number, Index> }
}
) => Promise<{ root: Link; index: RootIndex; blocks: Block[] }>
packGraph: (versionRoot: Link) => Promise<Block>
packComputed: (
versionRoot: Link,
vertexOffsetStart: number,
vertexCount: number,
graphDepth: number
) => Promise<Block>
indexCreate: (values: IndexedValue[]) => Promise<Link>
indexSearch: (link: Link, value: any) => Promise<IndexedValue>
constructor(
{
versionSet,
rootGet,
}: {
versionSet: ({
version,
index,
}: {
version: Version
index: RootIndex
}) => Promise<Link>
rootGet: () => Promise<{ root: Link; index: RootIndex }>
},
{
vertexGet,
vertexRange,
edgeGet,
edgeRange,
propGet,
propRange,
indexGet,
offsetsGet,
verticesAll,
edgesAll,
propsAll,
commit,
packGraph,
packComputed,
}: {
vertexGet: (
{ root, index }: { root: Link; index: RootIndex },
offset: number
) => Promise<Vertex>
vertexRange: (
{ root, index }: { root: Link; index: RootIndex },
offset: number,
vertexCount: number
) => Promise<Vertex[]>
edgeGet: (
{ root, index }: { root: Link; index: RootIndex },
offset: number
) => Promise<Edge>
edgeRange: (
{ root, index }: { root: Link; index: RootIndex },
offset: number,
edgeCount: number
) => Promise<Edge[]>
propGet: (
{ root, index }: { root: Link; index: RootIndex },
offset: number
) => Promise<Prop>
propRange: (
{ root, index }: { root: Link; index: RootIndex },
offset: number,
propCount: number
) => Promise<Prop[]>
indexGet: (
{ root, index }: { root: Link; index: RootIndex },
offset: number
) => Promise<Index>
offsetsGet: ({
root,
index,
}: {
root: Link
index: RootIndex
}) => Promise<{
vertexOffset: Offset
edgeOffset: Offset
propOffset: Offset
indexOffset: Offset
}>
verticesAll: ({
root,
index,
}: {
root: Link
index: RootIndex
}) => Promise<Vertex[]>
edgesAll: ({
root,
index,
}: {
root: Link
index: RootIndex
}) => Promise<Edge[]>
propsAll: ({
root,
index,
}: {
root: Link
index: RootIndex
}) => Promise<Prop[]>
commit: (
{ root, index }: { root: Link; index: RootIndex },
{
vertices,
edges,
props,
}: {
vertices: {
added: Map<number, Vertex>
updated: Map<number, Vertex>
}
edges: {
added: Map<number, Edge>
updated: Map<number, Edge>
}
props: {
added: Map<number, Prop>
updated: Map<number, Prop>
}
indices: {
added: Map<number, Index>
updated: Map<number, Index>
}
}
) => Promise<{ root: Link; index: RootIndex; blocks: Block[] }>
packGraph: (versionRoot: Link) => Promise<Block>
packComputed: (
versionRoot: Link,
vertexOffsetStart: number,
vertexCount: number,
graphDepth: number
) => Promise<Block>
},
{ indexCreate, indexSearch }: IndexStore = {
indexCreate: undefined,
indexSearch: undefined,
}
) {
this.vertices = new Map()
this.edges = new Map()
this.props = new Map()
this.indices = new Map()
this.versionSet = versionSet
this.rootGet = rootGet
this.vertexGet = vertexGet
this.vertexRange = vertexRange
this.edgeGet = edgeGet
this.edgeRange = edgeRange
this.propGet = propGet
this.propRange = propRange
this.indexGet = indexGet
this.offsetsGet = offsetsGet
this.verticesAll = verticesAll
this.edgesAll = edgesAll
this.propsAll = propsAll
this.commit = commit
this.packGraph = packGraph
this.packComputed = packComputed
this.indexCreate = indexCreate
this.indexSearch = indexSearch
}
async getVertex(ref: VertexRef): Promise<Vertex> {
let vertex: Vertex = this.vertices.get(ref)
if (vertex === undefined) {
vertex = await this.vertexGet(await this.rootGet(), ref)
this.vertices.set(ref, vertex)
}
return vertex
}
async getVertexRange(
startRef: VertexRef,
vertexCount: number
): Promise<Vertex[]> {
const vertices: Vertex[] = await this.vertexRange(
await this.rootGet(),
startRef,
vertexCount
)
let vertexRef = startRef
for (let i = 0; i < vertexCount; i++) {
this.vertices.set(vertexRef, vertices[i])
vertexRef += OFFSET_INCREMENTS.VERTEX_INCREMENT
}
return vertices
}
async getEdge(ref: EdgeRef): Promise<Edge> {
let edge: Edge = this.edges.get(ref)
if (edge === undefined) {
edge = await this.edgeGet(await this.rootGet(), ref)
this.edges.set(ref, edge)
}
return edge
}
async getEdgeRange(startRef: EdgeRef, edgeCount: number): Promise<Edge[]> {
const edges: Edge[] = await this.edgeRange(
await this.rootGet(),
startRef,
edgeCount
)
let edgeRef = startRef
for (let i = 0; i < edgeCount; i++) {
this.edges.set(edgeRef, edges[i])
edgeRef += OFFSET_INCREMENTS.EDGE_INCREMENT
}
return edges
}
async getProp(ref: PropRef): Promise<Prop> {
let prop: Prop = this.props.get(ref)
if (prop === undefined) {
prop = await this.propGet(await this.rootGet(), ref)
this.props.set(ref, prop)
}
return prop
}
async getPropRange(startRef: PropRef, propCount: number): Promise<Prop[]> {
const props: Prop[] = await this.propRange(
await this.rootGet(),
startRef,
propCount
)
let propRef = startRef
for (let i = 0; i < propCount; i++) {
this.props.set(propRef, props[i])
propRef += OFFSET_INCREMENTS.PROP_INCREMENT
}
return props
}
async getIndex(ref: IndexRef): Promise<Index> {
let index: Index = this.indices.get(ref)
if (index === undefined) {
index = await this.indexGet(await this.rootGet(), ref)
this.indices.set(ref, index)
}
return index
}
async allVertices(): Promise<Vertex[]> {
return await this.verticesAll(await this.rootGet())
}
async allEdges(): Promise<Edge[]> {
return await this.edgesAll(await this.rootGet())
}
async allProps(): Promise<Prop[]> {
return await this.propsAll(await this.rootGet())
}
async matchAnyEdgeProp(
edge: Edge,
predicate: PropPredicate
): Promise<boolean> {
if (edge.nextProp !== undefined)
return await this.matchNextProp(edge.nextProp, predicate)
else return false
}
async matchAnyVertexProp(
vertex: Vertex,
predicate: PropPredicate
): Promise<boolean> {
if (vertex.nextProp !== undefined)
return await this.matchNextProp(vertex.nextProp, predicate)
else return false
}
async matchNextProp(
ref: PropRef,
predicate: PropPredicate
): Promise<boolean> {
let prop: Prop = await this.getProp(ref)
if (
predicate.keyType === prop.key &&
predicate.operation.predicate(prop.value)
) {
return true
} else if (prop.nextProp !== undefined) {
return await this.matchNextProp(prop.nextProp, predicate)
}
return false
}
async matchAnyIndex(vertex: Vertex, key: KeyType): Promise<Index> {
return await this.matchNextIndex(vertex.nextIndex, key)
}
async matchNextIndex(
ref: IndexRef,
key: KeyType
): Promise<Index | undefined> {
if (ref !== undefined) {
const index: Index = await this.getIndex(ref)
if (index.key === key) return index
else return await this.matchNextIndex(index.nextIndex, key)
} else return undefined
}
async searchIndex(
index: Index,
predicate: PropPredicate
): Promise<IndexedValue> {
const { keyType, operation } = predicate
const indexedValue: IndexedValue = await this.indexSearch(
index.value,
operation.operand
)
return indexedValue
}
async getVertexEdges(vertex: Vertex): Promise<Edge[]> {
const edges: Edge[] = []
if (vertex.nextEdge !== undefined) {
await this.getNextEdges(vertex.nextEdge, edges)
}
return edges
}
async getNextEdges(ref: EdgeRef, edges: Edge[]): Promise<void> {
const edge: Edge = await this.getEdge(ref)
edges.push(edge)
if (edge.nextEdge !== undefined) {
await this.getNextEdges(edge.nextEdge, edges)
}
}
async getVertexProps(vertex: Vertex): Promise<Prop[]> {
const props: Prop[] = []
if (vertex.nextProp !== undefined) {
await this.getNextProps(vertex.nextProp, props)
}
return props
}
async getEdgeProps(edge: Edge): Promise<Prop[]> {
const props: Prop[] = []
if (edge.nextProp !== undefined) {
await this.getNextProps(edge.nextProp, props)
}
return props
}
async getNextProps(ref: PropRef, props: Prop[]): Promise<void> {
const prop: Prop = await this.getProp(ref)
props.push(prop)
if (prop.nextProp !== undefined) {
await this.getNextProps(prop.nextProp, props)
}
}
tx() {
return new Tx(this)
}
/**
* Pack a complete graph
* @param versionRoot - the version root
* @returns - a block containing the packed graph
*/
async packGraphVersion(versionRoot?: Link): Promise<Block> {
const { root } =
versionRoot === undefined
? await this.rootGet()
: { root: versionRoot }
return await this.packGraph(root)
}
/**
* Pack a graph fragment
*
* @param vertexOffsetStart - the offset of the first vertex
* @param vertexCount - the number of vertices, counting from the first vertex
* @param graphDepth - the depth of the fragment
* @param versionRoot - the version root
* @returns - a block containing the packed graph fragment
*/
async packGraphFragment(
vertexOffsetStart: number,
vertexCount: number,
graphDepth: number,
versionRoot?: Link
): Promise<Block> {
const { root } =
versionRoot === undefined
? await this.rootGet()
: { root: versionRoot }
return await this.packComputed(
root,
vertexOffsetStart,
vertexCount,
graphDepth
)
}
}
class Tx implements ElementAccessor {
graph: Graph
vertices: { added: Map<number, Vertex>; updated: Map<number, Vertex> }
edges: { added: Map<number, Edge>; updated: Map<number, Edge> }
props: { added: Map<number, Prop>; updated: Map<number, Prop> }
indices: { added: Map<number, Index>; updated: Map<number, Index> }
vertexOffsetInit: number
edgeOffsetInit: number
propOffsetInit: number
indexOffsetInit: number
vertexOffset: number
edgeOffset: number
propOffset: number
indexOffset: number
constructor(graph: Graph) {
this.graph = graph
}
async start(): Promise<Tx> {
this.vertices = { added: new Map(), updated: new Map() }
this.edges = { added: new Map(), updated: new Map() }
this.props = { added: new Map(), updated: new Map() }
this.indices = { added: new Map(), updated: new Map() }
const { root, index } = await this.graph.rootGet()
const { vertexOffset, edgeOffset, propOffset, indexOffset } =
root !== undefined
? await this.graph.offsetsGet({ root, index })
: {
vertexOffset: 0,
edgeOffset: 0,
propOffset: 0,
indexOffset: 0,
}
this.vertexOffsetInit = vertexOffset
this.edgeOffsetInit = edgeOffset
this.propOffsetInit = propOffset
this.indexOffsetInit = indexOffset
this.vertexOffset = vertexOffset
this.edgeOffset = edgeOffset
this.propOffset = propOffset
this.indexOffset = indexOffset
return this
}
nextVertexOffset(): Offset {
const currentOffset = this.vertexOffset
this.vertexOffset += OFFSET_INCREMENTS.VERTEX_INCREMENT
return currentOffset
}
nextEdgeOffset(): Offset {
const currentOffset = this.edgeOffset
this.edgeOffset += OFFSET_INCREMENTS.EDGE_INCREMENT
return currentOffset
}
nextPropOffset(): Offset {
const currentOffset = this.propOffset
this.propOffset += OFFSET_INCREMENTS.PROP_INCREMENT
return currentOffset
}
nextIndexOffset(): Offset {
const currentOffset = this.indexOffset
this.indexOffset += OFFSET_INCREMENTS.INDEX_INCREMENT
return currentOffset
}
async getVertex(ref: VertexRef): Promise<Vertex> {
let vertex: Vertex
if (ref >= this.vertexOffsetInit) {
vertex = this.vertices.added.get(ref)
if (vertex === undefined) vertex = this.vertices.updated.get(ref)
if (vertex === undefined)
throw new Error(`invalid vertex ref ${ref}`)
} else vertex = await this.graph.getVertex(ref)
return vertex
}
async getEdge(ref: EdgeRef): Promise<Edge> {
let edge: Edge
if (ref >= this.edgeOffsetInit) {
edge = this.edges.added.get(ref)
if (edge === undefined) edge = this.edges.updated.get(ref)
if (edge === undefined) throw new Error(`invalid edge ref ${ref}`)
} else edge = await this.graph.getEdge(ref)
return edge
}
async getProp(ref: PropRef): Promise<Prop> {
let prop: Prop
if (ref >= this.propOffsetInit) {
prop = this.props.added.get(ref)
if (prop === undefined) prop = this.props.updated.get(ref)
if (prop === undefined) throw new Error(`invalid prop ref ${ref}`)
} else prop = await this.graph.getProp(ref)
return prop
}
async getIndex(ref: IndexRef): Promise<Index> {
let index: Index
if (ref >= this.propOffsetInit) {
index = this.indices.added.get(ref)
if (index === undefined) index = this.indices.updated.get(ref)
if (index === undefined) throw new Error(`invalid index ref ${ref}`)
} else index = await this.graph.getProp(ref)
return index
}
notifyVertexUpdate(vertex: Vertex) {
if (!this.vertices.added.has(vertex.offset)) {
this.vertices.updated.set(vertex.offset, vertex)
}
}
notifyEdgeUpdate(edge: Edge) {
if (!this.edges.added.has(edge.offset)) {
this.edges.updated.set(edge.offset, edge)
}
}
notifyPropUpdate(prop: Prop) {
if (!this.props.added.has(prop.offset)) {
this.props.updated.set(prop.offset, prop)
}
}
notifyIndexUpdate(index: Index) {
if (!this.indices.added.has(index.offset)) {
this.indices.updated.set(index.offset, index)
}
}
addVertex(type?: VertexType): Vertex {
const offset = this.nextVertexOffset()
const vertex: Vertex = { status: Status.CREATED, offset }
if (type !== undefined) vertex.type = type
this.vertices.added.set(offset, vertex)
return vertex
}
async addEdge(
source: Vertex,
target: Vertex,
type?: EdgeType
): Promise<Edge> {
const offset = this.nextEdgeOffset()
const edge: Edge = {
status: Status.CREATED,
offset,
source: source.offset,
target: target.offset,
}
if (type !== undefined) edge.type = type
this.edges.added.set(edge.offset, edge)
if (source.nextEdge !== undefined) {
const nextEdge: Edge = await this.getEdge(source.nextEdge)
await this.appendEdge(nextEdge, edge)
} else {
source.nextEdge = offset
this.notifyVertexUpdate(source)
}
return edge
}
async linkVertexEdge(source: Vertex, edgeRef: EdgeRef): Promise<void> {
const edge: Edge = await this.getEdge(edgeRef)
if (source.nextEdge !== undefined) {
const nextEdge: Edge = await this.getEdge(source.nextEdge)
await this.appendEdge(nextEdge, edge)
} else {
source.nextEdge = edgeRef
this.notifyVertexUpdate(source)
}
}
async addVertexProp(
vertex: Vertex,
key: KeyType,
value: PropValue,
type?: PropType
): Promise<Prop> {
const offset = this.nextPropOffset()
const prop: Prop = { status: Status.CREATED, offset, key, value }
if (type !== undefined) prop.type = type
this.props.added.set(prop.offset, prop)
if (vertex.nextProp !== undefined) {
const nextProp: Prop = await this.getProp(vertex.nextProp)
await this.appendProp(nextProp, prop)
} else {
vertex.nextProp = offset
this.notifyVertexUpdate(vertex)
}
return prop
}
async linkVertexProp(source: Vertex, propRef: PropRef): Promise<void> {
const prop: Prop = await this.getProp(propRef)
if (source.nextProp !== undefined) {
const nextProp: Prop = await this.getProp(source.nextProp)
await this.appendProp(nextProp, prop)
} else {
source.nextProp = propRef
this.notifyVertexUpdate(source)
}
}
async addVertexIndex(
vertex: Vertex,
key: KeyType,
value: Link,
type?: IndexType
): Promise<Index> {
const offset = this.nextIndexOffset()
const index: Index = { status: Status.CREATED, offset, key, value }
if (type !== undefined) index.type = type
this.indices.added.set(index.offset, index)
if (vertex.nextIndex !== undefined)
await this.appendVertexIndex(vertex.nextIndex, index)
else {
vertex.nextIndex = offset
this.notifyVertexUpdate(vertex)
}
return index
}
async addEdgeProp(
edge: Edge,
key: KeyType,
value: PropValue,
type?: PropType
): Promise<Prop> {
const offset = this.nextPropOffset()
const prop: Prop = { status: Status.CREATED, offset, key, value }
if (type !== undefined) prop.type = type
this.props.added.set(prop.offset, prop)
if (edge.nextProp !== undefined) {
const nextProp: Prop = await this.getProp(edge.nextProp)
await this.appendProp(nextProp, prop)
} else {
edge.nextProp = offset
this.notifyEdgeUpdate(edge)
}
return prop
}
async linkEdgeProp(edge: Edge, propRef: PropRef): Promise<void> {
const prop: Prop = await this.getProp(propRef)
if (edge.nextProp !== undefined) {
const nextProp: Prop = await this.getProp(edge.nextProp)
await this.appendProp(nextProp, prop)
} else {
edge.nextProp = propRef
this.notifyEdgeUpdate(edge)
}
}
async linkEdge(currentEdge: Edge, edgeRef: EdgeRef): Promise<void> {
const edge: Edge = await this.getEdge(edgeRef)
await this.appendEdge(currentEdge, edge)
}
async appendEdge(currentEdge: Edge, newEdge: Edge): Promise<void> {
if (currentEdge.offset === newEdge.offset)
throw new Error(`Invalid edge append. Cannot append to itself`)
if (currentEdge.sourceNext !== undefined) {
const sourceNext: Edge = await this.getEdge(currentEdge.sourceNext)
await this.appendEdge(sourceNext, newEdge)
} else {
currentEdge.sourceNext = newEdge.offset
newEdge.sourcePrev = currentEdge.offset
this.notifyEdgeUpdate(currentEdge)
}
}
async linkProp(currentProp: Prop, propRef: PropRef): Promise<void> {
const prop: Prop = await this.getProp(propRef)
await this.appendProp(currentProp, prop)
}
async appendProp(currentProp: Prop, newProp: Prop): Promise<void> {
if (currentProp.offset === newProp.offset)
throw new Error(`Invalid prop append. Cannot append to itself`)
if (currentProp.nextProp !== undefined) {
const nextProp: Prop = await this.getProp(currentProp.nextProp)
await this.appendProp(nextProp, newProp)
} else {
currentProp.nextProp = newProp.offset
this.notifyPropUpdate(currentProp)
}
}
async appendVertexIndex(
currentRef: IndexRef,
newIndex: Index
): Promise<void> {
const currentIndex: Index = await this.getIndex(currentRef)
if (currentIndex.offset === newIndex.offset)
throw new Error(`Invalid index append. Cannot append to itself`)
if (currentIndex.nextIndex !== undefined) {
await this.appendVertexIndex(currentIndex.nextIndex, newIndex)
} else {
currentIndex.nextIndex = newIndex.offset
this.notifyIndexUpdate(currentIndex)
}
}
async uniqueIndex(
outgoingFrom: Vertex,
key: KeyType,
type?: IndexType
): Promise<Index | undefined> {
if (this.graph.indexCreate === undefined)
throw new Error('Please provide an index store to the graph')
const edgesOut = edgesOutgoing(outgoingFrom, this)
const values: IndexedValue[] = []
for await (const edge of edgesOut) {
const targetVertex: Vertex = await this.getVertex(edge.target)
const targetVertexProps = propsNext(targetVertex.nextProp, this)
for await (const prop of targetVertexProps) {
const propKey: KeyType = prop.key
if (propKey === key) {
const value = prop.value
values.push({ value, ref: edge.offset })
break
}
}
}
if (values.length === 0) return undefined
else {
const link: Link = await this.graph.indexCreate(values)
const index: Index = await this.addVertexIndex(
outgoingFrom,
key,
link,
type
)
return index
}
}
async commit({
comment,
tags,
signer,
}: {
comment?: Comment
tags?: Tag[]
signer?: Signer
}): Promise<{ root: Link; index: RootIndex; blocks: Block[] }> {
const verticesNew = new Map([
...this.graph.vertices,
...this.vertices.updated,
...this.vertices.added,
])
const edgesNew = new Map([
...this.graph.edges,
...this.edges.updated,
...this.edges.added,
])
const propsNew = new Map([
...this.graph.props,
...this.props.updated,
...this.props.added,
])
const indicesNew = new Map([
...this.graph.indices,
...this.indices.updated,
...this.indices.added,
])
const sourceVertexRefs: Set<VertexRef> = new Set(
Array.from(edgesNew.values()).map((edge) => edge.source)
)
const targetVertexRefs: Set<VertexRef> = new Set(
Array.from(edgesNew.values()).map((edge) => edge.target)
)
const editedVertexRefs: Set<VertexRef> = new Set(
Array.from(verticesNew.keys())
)
const impactedVertices = new Set([
...sourceVertexRefs,
...targetVertexRefs,
...editedVertexRefs,
])
await this.fillTargetLinks(impactedVertices)
const rootBefore = await this.graph.rootGet()
const { root, index, blocks } = await this.graph.commit(rootBefore, {
vertices: this.vertices,
edges: this.edges,
props: this.props,
indices: this.indices,
})
const versionDetails: VersionDetails = {
comment,
tags,
timestamp: Date.now(),
}
if (signer !== undefined) {
const signature = await signer.sign(root)
versionDetails.signature = signature
const publicKey = await signer.exportPublicKey()
versionDetails.author = publicKey
}
const version: Version = {
root,
parent: rootBefore.root,
details: versionDetails,
}
await this.graph.versionSet({ version, index })
this.graph.vertices = verticesNew
this.graph.edges = edgesNew
this.graph.props = propsNew
this.graph.indices = indicesNew
return { root, index, blocks }
}
async fillTargetLinks(refs: Iterable<VertexRef>): Promise<void> {
const incomingEdges = new Map<VertexRef, Set<EdgeRef>>()
await traverseVertices(this, refs, incomingEdgesVisitor(incomingEdges))
for (const [vertexRef, incomingRefs] of incomingEdges.entries()) {
const vertex: Vertex = await this.getVertex(vertexRef)
if (incomingRefs !== undefined) {
const outgoingEdges = edgesOutgoing(vertex, this)
// FIXME Poor alg assumes single incoming edge (ie tree struct), elaboration needed ...
// Should we to follow Neo4j's RelationshipRecord structure to store
// a bool when edge is first in chain (for both - src and target chains) ?
for await (const outgoingEdge of outgoingEdges) {
for (const incomingEdgeRef of incomingRefs.values()) {
const incomingEdge: Edge = await this.getEdge(
incomingEdgeRef
)
outgoingEdge.targetPrev = incomingEdge.offset
if (outgoingEdge.offset === vertex.nextEdge) {
incomingEdge.targetNext = outgoingEdge.offset
}
}
}
}
}
}
}
export { Graph, Tx, ElementAccessor }