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

604 lines (562 loc) 17.4 kB
import { Graph } from './graph' import { PredicateFactory, Predicate, Operation } from './ops' import { Edge, Vertex, Prop, Part, Ref, VertexRef, PropRef, EdgeRef, PropValue, KeyType, Type, IndexType, IndexRef, Index, IndexedValue, } from './types' enum PathElemType { VERTEX, EDGE, EXTRACT, TEMPLATE, } interface PropPredicate { keyType: KeyType operation: Operation } interface IndexPredicate extends PropPredicate {} interface PathElem { elemType: PathElemType } interface ReducedValue { context: Part value: PropValue } interface EdgePathElem extends PathElem { types?: Type[] propPredicate?: PropPredicate } interface VertexPathElem extends PathElem { types?: Type[] propPredicate?: PropPredicate } interface ExtractPathElem extends PathElem { props: number[] reduce?: (previous: PropValue, current: PropValue) => ReducedValue } interface TemplatePathElem extends PathElem { template: any } class RequestBuilder { path: PathElem[] request: NavigationThreshold constructor() { this.path = [] } add(elemType: PathElemType): RequestBuilder { if (elemType === PathElemType.EXTRACT) { throw new Error( 'For PathElemType.EXTRACT elemType please use the extract(...props) call' ) } let elem: PathElem = { elemType: elemType } this.path.push(elem) return this } type(...types: Type[]): RequestBuilder { const elemType = this.path[this.path.length - 1].elemType switch (elemType) { case PathElemType.VERTEX: ;(this.path[this.path.length - 1] as VertexPathElem).types = types break case PathElemType.EDGE: ;(this.path[this.path.length - 1] as EdgePathElem).types = types break default: throw new Error(`Cannot set predicate to ${elemType}`) } return this } propPred(keyType: KeyType, operation: Operation): RequestBuilder { const elemType = this.path[this.path.length - 1].elemType switch (elemType) { case PathElemType.VERTEX: ;( this.path[this.path.length - 1] as VertexPathElem ).propPredicate = { keyType, operation } break case PathElemType.EDGE: ;( this.path[this.path.length - 1] as EdgePathElem ).propPredicate = { keyType, operation } break default: throw new Error(`Cannot set prop predicate to ${elemType}`) } return this } extract(...props: number[]): RequestBuilder { let elem: ExtractPathElem = { elemType: PathElemType.EXTRACT, props } this.path.push(elem) return this } template(template: any): RequestBuilder { let elem: TemplatePathElem = { elemType: PathElemType.TEMPLATE, template, } this.path.push(elem) return this } reduce( reduceFunc: (previous: PropValue, current: PropValue) => ReducedValue ): RequestBuilder { const elemType = this.path[this.path.length - 1].elemType if (elemType !== PathElemType.EXTRACT) { throw new Error('Reduce should be applied to PathElemType.EXTRACT') } ;(this.path[this.path.length - 1] as ExtractPathElem).reduce = reduceFunc return this } maxResults(value: number): RequestBuilder { this.request = new NavigationThreshold(value) return this } get(): { path: PathElem[]; request: NavigationThreshold } { if (this.request === undefined) this.request = new NavigationThreshold(100) return { path: this.path, request: this.request } } } class SearchCompleted {} class NavigationThreshold { yieldCounter: number maxYield: number constructor(maxYield: number) { this.yieldCounter = 0 this.maxYield = maxYield } increment() { this.yieldCounter++ } isDone() { return this.yieldCounter === this.maxYield } completeWhenDone() { if (this.isDone()) { throw new SearchCompleted() } } } function checkFinished(index: number, path: PathElem[]): boolean { return index === path.length - 1 } function isExtractPathElem(index: number, path: PathElem[]): boolean { const pathElem: PathElem = path[index] return pathElem.elemType === PathElemType.EXTRACT } function isVertexPathElem(index: number, path: PathElem[]): boolean { const pathElem: PathElem = path[index] return pathElem.elemType === PathElemType.VERTEX } function isEdgePathElem(index: number, path: PathElem[]): boolean { const pathElem: PathElem = path[index] return pathElem.elemType === PathElemType.EDGE } function isTemplatePathElem(index: number, path: PathElem[]): boolean { const pathElem: PathElem = path[index] return pathElem.elemType === PathElemType.TEMPLATE } async function lookaheadIndexedPredicate( graph: Graph, vertex: Vertex, path: PathElem[], index: number ): Promise<{ propPredicate: PropPredicate; indexStruct: Index } | undefined> { const pathElem: PathElem = path[index] let found: { propPredicate: PropPredicate; indexStruct: Index } = undefined if (pathElem !== undefined) { if (pathElem.elemType === PathElemType.VERTEX) { const vertexPathElement = pathElem as VertexPathElem const propPredicate: PropPredicate = vertexPathElement.propPredicate if (propPredicate !== undefined) { const indexStruct = await graph.matchAnyIndex( vertex, propPredicate.keyType ) if (indexStruct !== undefined) { found = { propPredicate, indexStruct } } } } } return found } async function* yieldVertexOrGoDeeper( index: number, path: PathElem[], vertex: Vertex, graph: Graph, request: NavigationThreshold ): AsyncGenerator<any, void, void> { let indexedPredicate = await lookaheadIndexedPredicate( graph, vertex, path, index + 2 ) if (checkFinished(index, path)) { request.increment() yield vertex request.completeWhenDone() } else if (isExtractPathElem(index + 1, path)) yield* navigateProp( graph, vertex, vertex.nextProp, path, index + 1, request ) else if (isTemplatePathElem(index + 1, path)) { yield* navigateTemplate(graph, vertex, path, index + 1, request) } else if (indexedPredicate !== undefined) yield* navigateIndexedEdge( graph, indexedPredicate, path, index + 1, request ) else yield* navigateEdge(graph, vertex.nextEdge, path, index + 1, request) } async function* yieldEdgeOrGoDeeper( index: number, path: PathElem[], edge: Edge, graph: Graph, request: NavigationThreshold ): AsyncGenerator<any, void, void> { if (checkFinished(index, path)) { request.increment() yield edge request.completeWhenDone() } else if (isExtractPathElem(index + 1, path)) yield* navigateProp( graph, edge, edge.nextProp, path, index + 1, request ) else yield* navigateVertex(graph, edge.target, path, index + 1, request) } async function* navigateVertices( graph: Graph, refs: VertexRef[], { path, request }: { path: PathElem[]; request: NavigationThreshold } ): AsyncGenerator<any, void, void> { const index = 0 try { for (const ref of refs) { yield* navigateVertex(graph, ref, path, index, request) } } catch (e) { if (e instanceof SearchCompleted) { return } else throw e } } async function* navigateEdges( graph: Graph, refs: EdgeRef[], { path, request }: { path: PathElem[]; request: NavigationThreshold } ): AsyncGenerator<any, void, void> { const index = 0 try { for (const ref of refs) { yield* navigateEdge(graph, ref, path, index, request) } } catch (e) { if (e instanceof SearchCompleted) { return } else throw e } } async function* navigatePropCatchCompleted( graph: Graph, part: Part, ref: PropRef, path: PathElem[], index: number, request: NavigationThreshold ): AsyncGenerator<any, void, void> { try { yield* navigateProp(graph, part, ref, path, index, request) } catch (e) { if (e instanceof SearchCompleted) { return } else throw e } } async function* navigateEdgeCatchCompleted( graph: Graph, ref: EdgeRef, path: PathElem[], index: number, request: NavigationThreshold ): AsyncGenerator<any, void, void> { try { yield* navigateEdge(graph, ref, path, index, request) } catch (e) { if (e instanceof SearchCompleted) { return } else throw e } } async function* navigateVertex( graph: Graph, ref: VertexRef, path: PathElem[], index: number, request: NavigationThreshold ): AsyncGenerator<any, void, void> { const pathElem: VertexPathElem = path[index] as VertexPathElem if (ref !== undefined) { const vertex: Vertex = await graph.getVertex(ref) if (pathElem.types === undefined) { if (pathElem.propPredicate === undefined) { yield* yieldVertexOrGoDeeper( index, path, vertex, graph, request ) } else if ( await graph.matchAnyVertexProp(vertex, pathElem.propPredicate) ) { yield* yieldVertexOrGoDeeper( index, path, vertex, graph, request ) } } else if (pathElem.types.includes(vertex.type)) { if (pathElem.propPredicate === undefined) { yield* yieldVertexOrGoDeeper( index, path, vertex, graph, request ) } else if ( await graph.matchAnyVertexProp(vertex, pathElem.propPredicate) ) { yield* yieldVertexOrGoDeeper( index, path, vertex, graph, request ) } } } } async function* navigateEdge( graph: Graph, ref: EdgeRef, path: PathElem[], index: number, request: NavigationThreshold ): AsyncGenerator<Part, void, void> { const pathElem: EdgePathElem = path[index] as EdgePathElem if (ref !== undefined) { let edge: Edge = await graph.getEdge(ref) if (pathElem.types === undefined) { if (pathElem.propPredicate === undefined) { yield* yieldEdgeOrGoDeeper(index, path, edge, graph, request) } else if ( await graph.matchAnyEdgeProp(edge, pathElem.propPredicate) ) yield* yieldEdgeOrGoDeeper(index, path, edge, graph, request) } else if (pathElem.types.includes(edge.type)) { if (pathElem.propPredicate === undefined) { yield* yieldEdgeOrGoDeeper(index, path, edge, graph, request) } else if ( await graph.matchAnyEdgeProp(edge, pathElem.propPredicate) ) { yield* yieldEdgeOrGoDeeper(index, path, edge, graph, request) } } yield* navigateEdge(graph, edge.sourceNext, path, index, request) } } async function* navigateIndexedEdge( graph: Graph, indexedPredicate: { propPredicate: PropPredicate; indexStruct: Index }, path: PathElem[], index: number, request: NavigationThreshold ): AsyncGenerator<Part, void, void> { const { value, ref: edgeRef }: IndexedValue = await graph.searchIndex( indexedPredicate.indexStruct, indexedPredicate.propPredicate ) let edge: Edge = await graph.getEdge(edgeRef) yield* yieldEdgeOrGoDeeper(index, path, edge, graph, request) } async function* navigateProp( graph: Graph, partContext: Part, ref: PropRef, path: PathElem[], index: number, request: NavigationThreshold ): AsyncGenerator<any, void, void> { const pathElem: ExtractPathElem = path[index] as ExtractPathElem if (ref !== undefined) { let prop: Prop = await graph.getProp(ref) if (pathElem.reduce === undefined) { if (pathElem.props.includes(prop.key)) { request.increment() yield prop request.completeWhenDone() } yield* navigateProp( graph, partContext, prop.nextProp, path, index, request ) } else yield await navigatePropsAndReduce( graph, partContext, pathElem, prop, undefined ) } } async function navigatePropsAndReduce( graph: Graph, context: Part, pathElem: ExtractPathElem, prop: Prop, previousReduced: PropValue ): Promise<ReducedValue> { const reducedValue: PropValue = pathElem.reduce(previousReduced, prop.value) if (prop.nextProp !== undefined) { let nextProp: Prop = await graph.getProp(prop.nextProp) return navigatePropsAndReduce( graph, context, pathElem, nextProp, reducedValue ) } return { context, value: reducedValue } } async function* navigateTemplate( graph: Graph, vertex: Vertex, path: PathElem[], index: number, request: NavigationThreshold ): AsyncGenerator<any, void, void> { const pathElem: TemplatePathElem = path[index] as TemplatePathElem const template: any = pathElem.template const result = {} for (const [key, value] of Object.entries(template)) { const elemType = value['$elemType'] const schemaType = value['$type'] if (elemType === PathElemType.EXTRACT) { let propElem: ExtractPathElem = { elemType: PathElemType.EXTRACT, props: [schemaType], } const propPath = [...path.slice(0, index), propElem] for await (const prop of navigatePropCatchCompleted( graph, vertex, vertex.nextProp, propPath, index, new NavigationThreshold(Number.MAX_SAFE_INTEGER) )) { result[key] = prop.value } } else if (elemType === PathElemType.EDGE) { let edgeElem: EdgePathElem = { elemType: PathElemType.EDGE, types: [schemaType], } const edgePath = [...path.slice(0, index), edgeElem] const nestedResults = [] for await (const edge of navigateEdgeCatchCompleted( graph, vertex.nextEdge, edgePath, index, new NavigationThreshold(Number.MAX_SAFE_INTEGER) )) { const realEdge = edge as Edge const vertex: Vertex = await graph.getVertex(realEdge.target) const vertexElem: VertexPathElem = { elemType: PathElemType.VERTEX, } const templateElem: TemplatePathElem = { elemType: PathElemType.TEMPLATE, template: value, } const valueOnly = value['$value'] !== undefined const templateFragmentPath = [vertexElem, templateElem] for await (const nestedResult of navigateTemplate( graph, vertex, templateFragmentPath, 1, new NavigationThreshold(Number.MAX_SAFE_INTEGER) )) { const realNestedResult = valueOnly ? nestedResult.$value : nestedResult nestedResults.push(realNestedResult) } } if (nestedResults.length > 0) { result[key] = nestedResults } } } request.increment() yield result request.completeWhenDone() } export { PathElem, EdgePathElem, VertexPathElem, ExtractPathElem, TemplatePathElem, PropPredicate, PathElemType, RequestBuilder, NavigationThreshold, navigateVertices, navigateEdges, }