├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── Readme.md ├── docs └── images │ └── cypher-query-architecture.jpeg ├── jest.config.js ├── package.json ├── src ├── adapters │ ├── graph-api.ts │ ├── graph-obj-api.ts │ ├── graphology │ │ ├── graphology-graph-api.ts │ │ ├── graphology-obj-api.ts │ │ └── index.ts │ ├── gun │ │ ├── graph-api.ts │ │ ├── graph-obj-api.ts │ │ ├── index.ts │ │ └── search │ │ │ ├── bfs.ts │ │ │ ├── dfs.ts │ │ │ ├── index.ts │ │ │ ├── query-find.ts │ │ │ └── query-search.ts │ ├── index.ts │ └── utils.ts ├── cypher │ ├── builder │ │ ├── clause-builder.ts │ │ ├── error-handler.ts │ │ ├── handler.ts │ │ ├── index.ts │ │ ├── load │ │ │ ├── csv.ts │ │ │ └── index.ts │ │ ├── map.ts │ │ ├── query-builder.ts │ │ ├── read │ │ │ ├── expr-builder.ts │ │ │ ├── generic │ │ │ │ ├── index.ts │ │ │ │ └── number-expr-builder.ts │ │ │ ├── index.ts │ │ │ ├── match │ │ │ │ ├── index.ts │ │ │ │ ├── match-clause-builder.ts │ │ │ │ ├── match-expr-builder.ts │ │ │ │ └── match-obj-expr-builder.ts │ │ │ ├── result │ │ │ │ ├── index.ts │ │ │ │ ├── limit-expr-builder.ts │ │ │ │ ├── order-by-expr-builder.ts │ │ │ │ ├── result-clause-builder.ts │ │ │ │ ├── result-expr-builder.ts │ │ │ │ ├── result-number-expr-builder.ts │ │ │ │ ├── skip-expr-builder.ts │ │ │ │ └── union-expr-builder.ts │ │ │ ├── return │ │ │ │ ├── aggregation-expr-builder.ts │ │ │ │ ├── count-expr-builder.ts │ │ │ │ ├── index.ts │ │ │ │ ├── prop-expr-builder.ts │ │ │ │ ├── return-clause-builder.ts │ │ │ │ ├── return-expr-builder.ts │ │ │ │ └── select-alias-expr-builder.ts │ │ │ └── where │ │ │ │ ├── boolean │ │ │ │ ├── and-expr-builder.ts │ │ │ │ ├── boolean-expr-builder.ts │ │ │ │ ├── index.ts │ │ │ │ ├── not-expr-builder.ts │ │ │ │ └── or-expr-builder.ts │ │ │ │ ├── index.ts │ │ │ │ ├── label-expr-builder.ts │ │ │ │ ├── prop-expr-builder.ts │ │ │ │ ├── select-alias-expr-builder.ts │ │ │ │ ├── where-clause-builder.ts │ │ │ │ └── where-expr-builder.ts │ │ ├── return.ts │ │ └── write │ │ │ ├── constraint.ts │ │ │ ├── create.ts │ │ │ ├── delete.ts │ │ │ ├── index.ts │ │ │ ├── merge │ │ │ ├── index.ts │ │ │ ├── merge.ts │ │ │ ├── on-create-set.ts │ │ │ └── on-match-set.ts │ │ │ └── set.ts │ ├── cypher-types.ts │ ├── executer │ │ ├── executer.ts │ │ ├── index.ts │ │ ├── query-executer.ts │ │ └── transaction-executer.ts │ ├── index.ts │ ├── query.ts │ └── strategy │ │ ├── clauses │ │ ├── index.ts │ │ ├── match-clause.ts │ │ ├── match-clauses.ts │ │ ├── query-clause.ts │ │ ├── query-clauses.ts │ │ ├── result-clause.ts │ │ ├── result-clauses.ts │ │ ├── return-clause.ts │ │ ├── return-clauses.ts │ │ ├── where-clause.ts │ │ └── where-clauses.ts │ │ ├── controllers │ │ ├── base-controller.ts │ │ ├── index.ts │ │ ├── match-controller.ts │ │ ├── query-controller.ts │ │ ├── result-controller.ts │ │ ├── return-controller.ts │ │ └── where-controller.ts │ │ ├── defaults.ts │ │ ├── enum.ts │ │ ├── index.ts │ │ ├── map.ts │ │ ├── read │ │ ├── index.ts │ │ ├── match │ │ │ ├── index.ts │ │ │ └── match-obj-expr.ts │ │ ├── result │ │ │ ├── group-expr.ts │ │ │ ├── index.ts │ │ │ ├── limit-expr.ts │ │ │ ├── order-expr.ts │ │ │ ├── result.ts │ │ │ ├── skip-expr.ts │ │ │ └── union-expr.ts │ │ ├── return │ │ │ ├── aggregation-expr.ts │ │ │ ├── count-expr.ts │ │ │ ├── index.ts │ │ │ ├── label-expr.ts │ │ │ ├── prop-expr.ts │ │ │ ├── return-expr.ts │ │ │ ├── return-obj-value-expr.ts │ │ │ └── return-value.ts │ │ └── where │ │ │ ├── alias-filter.ts │ │ │ ├── boolean │ │ │ ├── and-expr.ts │ │ │ ├── index.ts │ │ │ ├── not.expr.ts │ │ │ ├── or-expr.ts │ │ │ └── set-operations.ts │ │ │ ├── compose-one-filter-expr.ts │ │ │ ├── composite-filter-expr.ts │ │ │ ├── filter-expr.ts │ │ │ ├── filter-result-converter.ts │ │ │ ├── index.ts │ │ │ ├── label │ │ │ ├── index.ts │ │ │ ├── node-label-compare-expr.ts │ │ │ ├── node-labels-include-expr.ts │ │ │ └── node-labels-match-expr.ts │ │ │ ├── node-pattern-expr.ts │ │ │ └── prop │ │ │ ├── index.ts │ │ │ ├── node-prop-compare-expr.ts │ │ │ ├── node-prop-eql-expr.ts │ │ │ ├── node-prop-gt-expr.ts │ │ │ └── node-prop-lt-expr.ts │ │ ├── strategy-handler.ts │ │ ├── strategy.ts │ │ ├── types.ts │ │ └── util.ts ├── hello-world.ts ├── index.ts └── types.ts ├── test └── cypher │ ├── builder │ ├── read │ │ ├── match │ │ │ ├── match-clause-builder.test.ts │ │ │ └── match-obj-expr-builder.test.ts │ │ ├── result │ │ │ ├── limit-expr-builder.test.ts │ │ │ ├── result-clause-builder.test.ts │ │ │ ├── skip-expr-builder.test.ts │ │ │ └── union-expr-builder.test.ts │ │ ├── return │ │ │ ├── aggregation-expr-builder.test.ts │ │ │ ├── count-expr-builder.test.ts │ │ │ ├── prop-expr-builder.test.ts │ │ │ └── return-clause-builder.test.ts │ │ └── where │ │ │ ├── boolean │ │ │ ├── and-expr-builder.test.ts │ │ │ ├── not-expr-builder.test.ts │ │ │ └── or-expr-builder.test.ts │ │ │ ├── label-expr-builder.test.ts │ │ │ ├── prop-expr-builder.test.ts │ │ │ └── where-clause-builder.test.ts │ └── write │ │ ├── create │ │ └── create.test.ts │ │ ├── delete │ │ └── delete.test.ts │ │ └── load │ │ └── csv.test.ts │ ├── fixtures │ ├── cars.ts │ ├── index.ts │ ├── owns.ts │ └── persons.ts │ ├── query.test.ts │ └── strategy │ ├── clauses │ ├── match-clause.test.ts │ ├── match-clauses.test.ts │ ├── result-clause.test.ts │ ├── result-clauses.test.ts │ ├── return-clause.test.ts │ ├── return-clauses.test.ts │ ├── where-clause.test.ts │ └── where-clauses.test.ts │ ├── controllers │ ├── match-controller.test.ts │ ├── query-controller.test.ts │ ├── result-controller.test.ts │ ├── return-controller.test.ts │ └── where-controller.test.ts │ ├── read │ ├── match │ │ └── match-obj-expr.test.ts │ ├── result │ │ ├── group-expr.test.ts │ │ ├── limit-expr.test.ts │ │ ├── order-expr.test.ts │ │ ├── skip-expr.test.ts │ │ └── union-expr.test.ts │ ├── return │ │ ├── aggregation-expr.test.ts │ │ ├── count-expr.test.ts │ │ ├── label-expr.test.ts │ │ └── prop-expr.test.ts │ └── where │ │ ├── alias-filter-expr.test.ts │ │ ├── boolean │ │ ├── and │ │ │ ├── and-expr.test.ts │ │ │ └── and-filter-results.test.ts │ │ ├── not │ │ │ ├── not-expr.test.ts │ │ │ └── not-filter-results.test.ts │ │ ├── or │ │ │ ├── or-expr.test.ts │ │ │ └── or-filter-results.test.ts │ │ └── set-operations.test.ts │ │ ├── compose-one-filter-expr.test.ts │ │ ├── composite-filter-expr.test.ts │ │ ├── filter-expr.test.ts │ │ ├── filter-result-converter.test.ts │ │ ├── label │ │ ├── node-labels-include-expr.test.ts │ │ └── node-labels-match-expr.test.ts │ │ ├── node-pattern-expr.test.ts │ │ └── prop │ │ ├── node-prop-eql-expr.test.ts │ │ ├── node-prop-gt-expr.test.ts │ │ └── node-prop-lt-expr.test.ts │ └── strategy.test.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "node", 5 | "name": "vscode-jest-tests.v2", 6 | "request": "launch", 7 | "console": "integratedTerminal", 8 | "internalConsoleOptions": "neverOpen", 9 | "disableOptimisticBPs": true, 10 | "program": "${workspaceFolder}/jest", 11 | "cwd": "${workspaceFolder}", 12 | "args": [ 13 | "--runInBand", 14 | "--watchAll=false", 15 | "--testNamePattern", 16 | "${jest.testNamePattern}", 17 | "--runTestsByPath", 18 | "${jest.testFile}" 19 | ] 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "jest.jestCommandLine": "jest" 3 | } -------------------------------------------------------------------------------- /docs/images/cypher-query-architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/docs/images/cypher-query-architecture.jpeg -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypher-query", 3 | "version": "0.1.0", 4 | "description": "Graph DB query engine for Cypher (CQL) that aims to support Graphology and Gun", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "/dist" 9 | ], 10 | "scripts": { 11 | "test": "jest", 12 | "build": "rm -rf ./dist && tsc", 13 | "gun:server": "gun --host 127.0.0.1" 14 | }, 15 | "keywords": [ 16 | "cypher", 17 | "neo4j", 18 | "graphdb", 19 | "graph", 20 | "query", 21 | "builder", 22 | "engine", 23 | "graphology", 24 | "gun", 25 | "web3" 26 | ], 27 | "author": "Kristian Mandrup ", 28 | "license": "ISC", 29 | "dependencies": { 30 | "@types/jest": "^27.0.3", 31 | "csvtojson": "^2.0.10", 32 | "graphology": "^0.23.2", 33 | "graphology-neo4j": "^1.2.0", 34 | "graphology-operators": "^1.5.0", 35 | "gun": "^0.2020.1235", 36 | "jest": "^27.4.7", 37 | "request": "^2.88.2", 38 | "ts-jest": "^27.1.2", 39 | "typescript": "^4.5.4" 40 | }, 41 | "devDependencies": { 42 | "@types/gun": "^0.9.3", 43 | "@types/request": "^2.48.7", 44 | "graphology-types": "^0.23.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/adapters/graph-api.ts: -------------------------------------------------------------------------------- 1 | import { NodeDef, RelationDef } from "../cypher/cypher-types"; 2 | 3 | export interface IGraphApi { 4 | createNode(opts: NodeDef): any; 5 | createEdge(fromId: string, toId: string, edgeDef: RelationDef): any; 6 | } 7 | -------------------------------------------------------------------------------- /src/adapters/graph-obj-api.ts: -------------------------------------------------------------------------------- 1 | export interface IGraphObjApi { 2 | propValue(node: any, propName: string): any; 3 | nodeLabels(node: any): string[]; 4 | } 5 | -------------------------------------------------------------------------------- /src/adapters/graphology/graphology-graph-api.ts: -------------------------------------------------------------------------------- 1 | import Graph from "graphology"; 2 | import { IGraphApi } from ".."; 3 | 4 | import { GraphObjDef, NodeDef, RelationDef } from "../../cypher/cypher-types"; 5 | 6 | import { uuidv4 } from "../utils"; 7 | 8 | export class MemDB { 9 | graph: Graph; 10 | 11 | constructor(graph?: Graph) { 12 | this.graph = graph || new Graph(); 13 | } 14 | 15 | addNode(id: string) { 16 | return this.graph.addNode({ id }); 17 | } 18 | 19 | addEdge(fromId: string, toId: string, edgeDef: any) { 20 | return this.graph.addEdge(fromId, toId, edgeDef); 21 | } 22 | 23 | edges() { 24 | return this.graph.edges(); 25 | } 26 | 27 | nodes() { 28 | return this.graph.nodes(); 29 | } 30 | } 31 | 32 | export class GraphologyGraphApi implements IGraphApi { 33 | memdb: MemDB; 34 | 35 | constructor(memdb: any) { 36 | this.memdb = memdb; 37 | } 38 | 39 | protected validateLabel(label: string) { 40 | return label.trim().length; 41 | } 42 | 43 | protected filterLabels(labels: string[]) { 44 | return labels.filter(this.validateLabel); 45 | } 46 | 47 | createNode(opts: NodeDef) { 48 | const id = uuidv4(); 49 | const object = this.memdb.addNode(id); 50 | return this.setNode(object, opts); 51 | } 52 | 53 | createEdge(fromId: string, toId: string, edgeDef: RelationDef) { 54 | const id = uuidv4(); 55 | return this.memdb.addEdge(fromId, toId, edgeDef); 56 | } 57 | 58 | protected setObj(object: any, opts: GraphObjDef) { 59 | const label = opts.label && [opts.label]; 60 | const labels = label || opts.labels || []; 61 | const props = opts.props || {}; 62 | object.__labels = this.filterLabels(labels); 63 | object.__props = props; 64 | return object; 65 | } 66 | 67 | /* Schema for Nodes */ 68 | setNode(object: any, nodeDef: NodeDef) { 69 | this.setObj(object, nodeDef); 70 | object.__type = "node"; 71 | return nodeDef; 72 | } 73 | 74 | createRel(fromNode: any, relation: RelationDef, toNode: any) { 75 | // 76 | } 77 | 78 | /* Schema for Edges */ 79 | setEdge(object: any, relDef: RelationDef) { 80 | this.setObj(object, relDef); 81 | object.__type = "edge"; 82 | return relDef; 83 | } 84 | 85 | nodes = () => { 86 | return this.memdb.nodes(); 87 | }; 88 | 89 | //.put({'__type':'index','__label':'edgesIndex'}); //same here 90 | edges = () => { 91 | return this.memdb.edges(); 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /src/adapters/graphology/graphology-obj-api.ts: -------------------------------------------------------------------------------- 1 | import { IGraphObjApi } from "../.."; 2 | 3 | export class GraphologyObjApi implements IGraphObjApi { 4 | propValue(node: any, propName: string) { 5 | const props = node["__props"] || {}; 6 | return props[propName]; 7 | } 8 | 9 | nodeLabels(node: any): string[] { 10 | return node["__labels"] || []; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/adapters/graphology/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./graphology-graph-api"; 2 | export * from "./graphology-obj-api"; 3 | -------------------------------------------------------------------------------- /src/adapters/gun/graph-api.ts: -------------------------------------------------------------------------------- 1 | import Gun from "gun"; 2 | import "gun/lib/then"; 3 | import { 4 | GraphObjDef, 5 | NodeDef, 6 | NodeRelOpts, 7 | RelationDef, 8 | RelSetArgs, 9 | } from "../../cypher/cypher-types"; 10 | 11 | /* Abstraction Layer to GunDB 12 | * Functions to abstract the creation of a schema 13 | */ 14 | 15 | import { DFS } from "./search"; 16 | import { IPromisedChain } from "../../types"; 17 | import { IGraphApi } from ".."; 18 | 19 | /* Schema definitions: 20 | * Metadata should start with double-underscore 21 | * all items should include __label and __type with the corresponding type 22 | * __label = a name that visualGraph can display for the node 23 | * __type = either 'node', 'edge' or 'index', this will become important 24 | * when querying happens 25 | */ 26 | 27 | export class GunAPI implements IGraphApi { 28 | gun: any; 29 | $nodes: any; 30 | $edges: any; 31 | dfs: DFS; 32 | 33 | constructor(gun: any) { 34 | this.gun = gun; 35 | this.dfs = new DFS(gun); 36 | } 37 | 38 | /* 39 | Allowing 2 users to collaborate on the same path. 40 | This makes it write only for each user, but in a UI using below function you 41 | can now see whatever was added latest collaboration 42 | */ 43 | 44 | async getLatest(path: string[], pubkeyOther: string) { 45 | let refMe = this.gun.user(); 46 | let refBob = this.gun.user(pubkeyOther); 47 | console.log("me", refMe); 48 | console.log("bob", refBob); 49 | while (path.length > 0) { 50 | const step = path.shift(); 51 | if (step) { 52 | refMe = refMe.get(step); 53 | refBob = refBob.get(step); 54 | } 55 | } 56 | let me: any, bob: any; 57 | me = await (refMe as IPromisedChain).promise(); 58 | console.log("me", me.data); 59 | bob = await (refBob as IPromisedChain).promise(); 60 | console.log("bob", bob.data); 61 | return Gun.node.soul(me.data); //, bob.data); 62 | } 63 | 64 | protected validateLabel(label: string) { 65 | return label.trim().length; 66 | } 67 | 68 | protected filterLabels(labels: string[]) { 69 | return labels.filter(this.validateLabel); 70 | } 71 | 72 | createNode(opts: NodeDef) { 73 | const object = this.gun.get("node"); 74 | return this.setNode(object, opts); 75 | } 76 | 77 | createEdge(fromId: string, toId: string, edgeDef: RelationDef) { 78 | return {}; 79 | } 80 | 81 | protected setObj(object: any, opts: GraphObjDef) { 82 | const label = opts.label && [opts.label]; 83 | const labels = label || opts.labels || []; 84 | const props = opts.props || {}; 85 | object.__labels = this.filterLabels(labels); 86 | object.__props = props; 87 | return object; 88 | } 89 | 90 | /* Schema for Nodes */ 91 | setNode(object: any, opts: NodeDef) { 92 | this.setObj(object, opts); 93 | object.__type = "node"; 94 | const gunRef = this.nodes().set(object); 95 | return gunRef; 96 | } 97 | 98 | createRel(fromNode: any, relation: RelationDef, toNode: any) { 99 | return this.tuple(fromNode, relation, toNode); 100 | } 101 | 102 | /* Schema for Edges */ 103 | setEdge(object: any, opts: NodeDef) { 104 | this.setObj(object, opts); 105 | object.__type = "edge"; 106 | const gunRef = this.edges().set(object); 107 | return gunRef; 108 | } 109 | 110 | /* Create Index for Nodes and Edges */ 111 | //.put({'__type':'index','__label':'nodesIndex'}); //creates global nodes index, but not write protected 112 | nodes = () => { 113 | this.$nodes = this.$nodes || this.gun.get("nodes"); 114 | return this.$nodes; 115 | }; 116 | 117 | //.put({'__type':'index','__label':'edgesIndex'}); //same here 118 | edges = () => { 119 | this.$edges = this.$edges || this.gun.get("edges"); 120 | return this.$edges; 121 | }; 122 | 123 | // TODO: support matching multiple labels and props 124 | matchNode(node: NodeDef, relOpts: RelSetArgs = {}) { 125 | return this.dfs.search(this.gun, node.label); 126 | } 127 | 128 | matchRel(node: NodeDef, nodeRelOpts: NodeRelOpts = {}) { 129 | return this.dfs.search(this.gun, node.label, nodeRelOpts); 130 | } 131 | 132 | async matchNodeAsync(opts: NodeDef, relOpts: RelSetArgs) { 133 | return await this.dfs.searchAsync(this.gun, opts.label); 134 | } 135 | 136 | /* Tuple function */ 137 | /* Takes objects or references from Gun to create nodes */ 138 | tuple(fromNode: any, relationship: any, toNode: any) { 139 | const relNode = relationship.__labels 140 | ? relationship 141 | : this.gun.get(relationship); 142 | const { direction } = relationship; 143 | if (direction !== "from") { 144 | fromNode.get("out").set(relNode); 145 | } 146 | 147 | relNode.get("source").put(fromNode); 148 | relNode.get("target").put(toNode); 149 | if (direction !== "to") { 150 | toNode.get("in").set(relNode); 151 | } 152 | return { 153 | from: fromNode, 154 | to: toNode, 155 | relation: relNode, 156 | }; 157 | // setTimeout(() => this.dfs.search("nodes", "__labels"), 1000); 158 | } 159 | 160 | addNode(edgeR: any, nodeR: any, ...labels: string[]) { 161 | const obj = { labels: labels, edge: edgeR, node: nodeR }; 162 | this.nodes() 163 | .map() 164 | .once((obj: any, data: any, key: string) => { 165 | if (data.__labels.find(obj.label)) { 166 | const soul = Gun.node.soul(data); 167 | let node = this.gun.get(soul).get("out").set(obj.edge); 168 | console.log(node._.soul); 169 | node = this.gun.get(node._.soul); 170 | obj.edge.get("source").put(node); 171 | obj.edge.get("target").put(obj.node); 172 | obj.node.get("in").set(obj.edge); 173 | } else { 174 | console.log(`${obj.label}, not found`); 175 | } 176 | }); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/adapters/gun/graph-obj-api.ts: -------------------------------------------------------------------------------- 1 | import { IGraphObjApi } from "../.."; 2 | 3 | export class GunObjApi implements IGraphObjApi { 4 | propValue(node: any, propName: string) { 5 | const props = node["__props"] || {}; 6 | return props[propName]; 7 | } 8 | 9 | nodeLabels(node: any): string[] { 10 | return node["__labels"] || []; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/adapters/gun/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./graph-api"; 2 | export * from "../utils"; 3 | -------------------------------------------------------------------------------- /src/adapters/gun/search/bfs.ts: -------------------------------------------------------------------------------- 1 | import Gun from "gun"; 2 | 3 | //TODO: Finish this 4 | 5 | /* Breadth First Search - explore all of the nodes from the given Soul 6 | * then update D3 data and the force-layout from the html 7 | */ 8 | // Breadth First Search 9 | export class BFS { 10 | u: any; 11 | opt = true; 12 | label = "label"; 13 | start = "startSould"; 14 | stack: any[] = []; 15 | nodes = new Map(); 16 | edges = new Map(); 17 | root: any; 18 | 19 | constructor(root: any) { 20 | this.root = root; 21 | } 22 | 23 | async run() { 24 | const start = await this.root.get(this.start).promise(); 25 | 26 | this.nodes.set(start.key, { 27 | id: start.key, 28 | label: start.data.label, 29 | data: start.data, 30 | }); 31 | this.u = start; 32 | this.stack.push(this.u); 33 | 34 | do { 35 | while (!this.exhausted(this.u.data, this.edges)) { 36 | // release control on each loop for ui 37 | await this.delay(20); //play with this value 38 | var edge = this.exhausted(this.u.data, this.edges, true); 39 | var v = await this.root.get(edge).promOnce(); 40 | this.nodes.set(v.key, { id: v.key, label: v.data.label, data: v.data }); 41 | this.edges.set(this.u.key + v.key, { 42 | source: this.u.key, 43 | target: v.key, 44 | }); 45 | this.stack.push(v); 46 | } 47 | while (!(this.stack.length == 0)) { 48 | await this.delay(20); 49 | const y = this.stack.shift(); 50 | if (!this.exhausted(y.data, this.edges)) { 51 | this.stack.push(y); 52 | this.u = y; 53 | break; 54 | } 55 | } 56 | } while (!(this.stack.length == 0)); 57 | } 58 | 59 | // console.log(nodes, edges); 60 | 61 | exhausted(node: any, edges: any, opt?: any) { 62 | var soul = Gun.node.soul(node); 63 | var arr = Object.keys(node); 64 | var i = 1; 65 | var l = arr.length; 66 | for (; i < l; i++) { 67 | if (typeof node[arr[i]] == "object" && node[arr[i]] != null) { 68 | if (!edges.has(soul + node[arr[i]]["#"])) { 69 | var temp = node[arr[i]]["#"]; 70 | break; 71 | } 72 | } 73 | } 74 | if (!opt) { 75 | if (temp) { 76 | return false; 77 | } else { 78 | return true; 79 | } 80 | } else { 81 | if (temp) { 82 | return temp; 83 | } 84 | } 85 | } 86 | 87 | transformMap(map: any) { 88 | var array: any[] = Array.from(map); 89 | var result = []; 90 | for (let i = 0; i < array.length; i++) { 91 | result.push(array[i][1]); 92 | } 93 | return result; 94 | } 95 | 96 | delay(ms: number) { 97 | return new Promise((res) => { 98 | setTimeout(res, ms); 99 | }); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/adapters/gun/search/dfs.ts: -------------------------------------------------------------------------------- 1 | import Gun from "gun"; 2 | 3 | /* Depth First Search - explore all of the nodes from the given Soul 4 | * then update D3 data and the force-layout from the html 5 | */ 6 | 7 | export type ItemMatchFn = (item: any) => boolean; 8 | 9 | export const defaults = { 10 | matches: (item: any, ctx: any) => { 11 | return item == ctx.label; 12 | }, 13 | }; 14 | 15 | export class DFS { 16 | gun: any; 17 | stack: any[] = []; 18 | nodes: any; 19 | edges: any; 20 | start: any; 21 | u: any; 22 | label: string = ""; 23 | opt = false; 24 | stop = false; 25 | limit = 500; 26 | 27 | constructor(gun: any, opts: any = {}) { 28 | this.gun = gun; 29 | this.limit = opts.limit || this.limit || 500; 30 | } 31 | 32 | printMap(map: any) { 33 | const array: any[] = Array.from(map); 34 | var l = array.length; 35 | for (let i = 0; i < l; i++) { 36 | console.log(array[i][1]); 37 | } 38 | } 39 | 40 | printArr(array: any[]) { 41 | for (let i = 0; i < array.length; i++) { 42 | console.log(array[i]); 43 | } 44 | } 45 | 46 | makeNodes(map: any) { 47 | const array: any[] = Array.from(map); 48 | var nodes = []; 49 | for (let i = 0; i < array.length; i++) { 50 | nodes.push(array[i][1]); 51 | } 52 | return nodes; 53 | } 54 | 55 | makeEdges(map: any) { 56 | const array: any[] = Array.from(map); 57 | var edges = []; 58 | for (let i = 0; i < array.length; i++) { 59 | edges.push(array[i][1]); 60 | } 61 | return edges; 62 | } 63 | 64 | async searchAsync(soul: any, lbl: any, lim?: any) {} 65 | 66 | search(soul: any, lbl: any, lim?: any) { 67 | console.log("Starting with:", soul); 68 | if (lbl) { 69 | this.opt = true; 70 | } else { 71 | this.opt = false; 72 | } 73 | if (lim) { 74 | this.limit = lim; 75 | } 76 | console.log(this.limit); 77 | this.label = lbl; 78 | this.start = soul; 79 | this.stack = []; 80 | this.nodes = new Map(); 81 | this.edges = new Map(); 82 | this.gun.get(soul).once(this.node); 83 | } 84 | 85 | node(node: any, key: string) { 86 | //console.log('called', nodes.size); 87 | if (!node) { 88 | console.error("no data:", key, node); 89 | this.back(); 90 | return; 91 | } 92 | let soul = Gun.node.soul(node); 93 | if (soul == this.start) { 94 | this.stack.push(soul); 95 | } 96 | this.u = node; 97 | if (!this.opt || node[this.label] == undefined) { 98 | this.nodes.set(soul, { id: soul, label: key }); 99 | } else { 100 | this.nodes.set(soul, { id: soul, label: node[this.label] }); 101 | } 102 | this.edge(this.u, this.edges); 103 | } 104 | 105 | edge(node: any, edges: any, matches = defaults.matches) { 106 | if (this.stop) { 107 | console.log("stopped"); 108 | return; 109 | } 110 | let temp: any; 111 | let soul = Gun.node.soul(node); 112 | let tLabel = "none"; 113 | let arr = Object.keys(node); 114 | for (let item of arr) { 115 | //save label if the prop meets the label 116 | if (matches(item, this)) { 117 | tLabel = node[item]; 118 | } 119 | //console.log(tLabel); 120 | // if it's an object, then there is more 121 | if (typeof node[item] == "object") { 122 | //skip nulled items or metadata 123 | if (node[item] == null || item == "_") { 124 | continue; 125 | } 126 | if (!edges.has(soul + node[item]["#"])) { 127 | temp = node[item]; 128 | break; 129 | } 130 | } 131 | } 132 | if (temp) { 133 | this.next(temp, soul, temp["#"], tLabel); 134 | } else { 135 | if (this.start == soul) { 136 | this.stack.pop(); 137 | } 138 | this.back(); 139 | } 140 | } 141 | 142 | next(next: any, edgeS: any, edgeT: any, tLabel: string) { 143 | let v = next; 144 | let soul = v["#"]; 145 | this.nodes.set(soul, { id: soul, label: v["#"] }); 146 | this.edges.set(edgeS + edgeT, { source: edgeS, target: edgeT }); 147 | this.stack.push(soul); 148 | this.u = v; 149 | if (this.nodes.size >= this.limit) { 150 | console.info("Reached limit"); 151 | return; 152 | } 153 | this.gun.get(soul).once(this.node); 154 | } 155 | 156 | back() { 157 | if (this.stack.length == 0) return; 158 | let soul = this.stack.pop(); 159 | this.gun.get(soul).once(this.node); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/adapters/gun/search/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./bfs"; 2 | export * from "./dfs"; 3 | export * from "./query-find"; 4 | export * from "./query-search"; 5 | -------------------------------------------------------------------------------- /src/adapters/gun/search/query-find.ts: -------------------------------------------------------------------------------- 1 | import { DFS } from "."; 2 | import { GunAPI } from "../../.."; 3 | 4 | export class QueryFind { 5 | tObj: any; 6 | 7 | schema: GunAPI; 8 | dfs: DFS; 9 | 10 | constructor(schema: GunAPI) { 11 | this.schema = schema; 12 | this.dfs = new DFS(schema); 13 | } 14 | 15 | isMatch(is: string[], toMatch: string[]) { 16 | return toMatch.some(function (v: string) { 17 | return is.indexOf(v) >= 0; 18 | }); 19 | } 20 | 21 | print() { 22 | console.log("Found:"); 23 | const arr = Object.entries(this.tObj.result[0]); 24 | const i = 0; 25 | const l = arr.length; 26 | for (let i = 0; i < l; i++) { 27 | console.log(arr[i][0]); 28 | console.log(arr[i][1]); 29 | } 30 | this.dfs.search("nodes", "__labels"); 31 | } 32 | 33 | static find(schema: GunAPI, obj: any) { 34 | return new QueryFind(schema).find(obj); 35 | } 36 | 37 | nodes() { 38 | return this.schema.nodes(); 39 | } 40 | 41 | find(obj: any) { 42 | this.tObj = obj; 43 | this.nodes().map().once(this.match); 44 | } 45 | 46 | match(node: any, key: string) { 47 | if (typeof node != "string") { 48 | const keysQ = Object.keys(this.tObj.obj); 49 | const keys = Object.keys(node); 50 | const soul = node._.soul; 51 | if (this.isMatch(keys, keysQ)) { 52 | for (let i = 0; i < keys.length; i++) { 53 | for (let y = 0; y < keysQ.length; y++) { 54 | const qK = keysQ[y]; 55 | const qV = this.tObj.obj[qK]; 56 | const k = keys[i]; 57 | const v = node[k]; 58 | if (qV == v && k == qK) { 59 | this.tObj.result.push(node); 60 | this.print(); 61 | } 62 | } 63 | } 64 | } else { 65 | console.log("no match!"); 66 | return; 67 | } 68 | } else { 69 | return; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/adapters/gun/search/query-search.ts: -------------------------------------------------------------------------------- 1 | import Gun from "gun"; 2 | import { GunAPI } from "../../.."; 3 | import { DFS } from "./dfs"; 4 | 5 | /* BFS Search for Pattern (Query) */ 6 | 7 | export interface SearchResult { 8 | item: any; 9 | node: any; 10 | } 11 | 12 | type ResultReceiver = (result: SearchResult) => any; 13 | 14 | type SearchCreateOpts = { log?: boolean; resultReceiver?: ResultReceiver }; 15 | 16 | export class QuerySearch { 17 | log: boolean = false; 18 | tObj: any = {}; 19 | gun: any; 20 | schema: GunAPI; 21 | dfs: DFS; 22 | resultReceiver?: ResultReceiver; 23 | 24 | constructor(schema: GunAPI, opts: SearchCreateOpts = {}) { 25 | this.schema = schema; 26 | this.gun = schema.gun; 27 | this.dfs = new DFS(schema); 28 | this.setOptions(opts); 29 | } 30 | 31 | setOptions(opts: SearchCreateOpts = {}) { 32 | this.resultReceiver = opts.resultReceiver; 33 | this.log = opts.log || this.log; 34 | return this; 35 | } 36 | 37 | static search(schema: GunAPI, obj: any) { 38 | return new QuerySearch(schema).search(obj); 39 | } 40 | 41 | nodes() { 42 | return this.schema.nodes(); 43 | } 44 | 45 | search(obj: any) { 46 | this.tObj = obj; 47 | if (this.tObj.subject) { 48 | this.nodes().map().once(this.step); 49 | } else { 50 | throw "no pattern defined"; 51 | } 52 | } 53 | 54 | async searchAsync() {} 55 | 56 | step(node: any, key?: string) { 57 | if (typeof node != "string") { 58 | const soul = Gun.node.soul(node); /*node['_']['#'] || node['#']*/ 59 | console.log(soul); 60 | if (this.matches(node, "subject")) { 61 | this.gun.get(soul).get("out").map().once(this.look.bind(null, soul)); 62 | } 63 | } 64 | } 65 | 66 | protected matches(node: any, propName: string) { 67 | return ( 68 | node["__labels"].find(this.tObj[propName]) || 69 | this.tObj[propName][0] == "?" 70 | ); 71 | } 72 | 73 | look(parent: any, node: any, key: string) { 74 | console.log("Qlook", key, node, parent); 75 | const soul = Gun.node.soul(node); /*node['_']['#'] || node['#']*/ 76 | if (this.matches(node, "predicate")) { 77 | const temp = parent + "__" + soul; 78 | this.gun.get(node.target["#"]).once(this.find.bind(null, temp)); 79 | } 80 | } 81 | 82 | find(parent: any, node: any, key: string) { 83 | console.log("Qfind", key, node, parent); 84 | const soul = Gun.node.soul(node); /*node['_']['#'] || node['#']*/ 85 | if (this.matches(node, "subject")) { 86 | const temp = parent + "__" + soul; 87 | console.log("pushed", temp); 88 | this.tObj.result.push(temp); 89 | this.presentResult(); 90 | } 91 | } 92 | 93 | presentResultNotFound() { 94 | if (this.tObj.result) return false; 95 | console.log("no results"); 96 | return true; 97 | } 98 | 99 | presentResultFound() { 100 | const l = this.tObj.result.length; 101 | for (let i = 0; i < l; i++) { 102 | let temp = this.tObj.result[i]; 103 | temp = temp.split("__"); 104 | const l1 = temp.length; 105 | for (let y = 0; y < l1; y++) { 106 | this.gun.get(temp[y]).once(this.nice.bind(null, y)); 107 | } 108 | } 109 | this.dfs.search("nodes", "__label"); 110 | return; 111 | } 112 | 113 | presentResult() { 114 | return this.presentResultNotFound() || this.presentResultFound(); 115 | } 116 | 117 | logResult({ item, node }: SearchResult) { 118 | if (!this.log) return; 119 | console.log(item, { labels: node["__labels"], props: node["__props"] }); 120 | } 121 | 122 | notify(result: SearchResult) { 123 | if (!this.resultReceiver) return; 124 | this.resultReceiver(result); 125 | } 126 | 127 | nice(item: any, node: any) { 128 | const result = { item, node }; 129 | this.notify(result); 130 | this.logResult(result); 131 | return result; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/adapters/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./gun"; 2 | export * from "./graph-api"; 3 | export * from "./graph-obj-api"; 4 | export * from "./graphology"; 5 | export * from "./utils"; 6 | -------------------------------------------------------------------------------- /src/adapters/utils.ts: -------------------------------------------------------------------------------- 1 | /* Triple Traversal Object 2 | * This stores options and is meant to become the transport object between 3 | * functions that execute during Traversal 4 | */ 5 | 6 | interface ITriple { 7 | subject: any; 8 | predicate: any; 9 | object: any; 10 | result?: any[]; 11 | } 12 | 13 | export class TripTrav { 14 | subject: any; 15 | predicate: any; 16 | object: any; 17 | result: any[]; 18 | 19 | constructor(triple: ITriple) { 20 | this.subject = triple.subject; 21 | this.predicate = triple.predicate; 22 | this.object = triple.object; 23 | this.result = []; 24 | } 25 | } 26 | 27 | /* Select an object to match from a node */ 28 | 29 | export class SelectTrav { 30 | obj: any; 31 | result: any[]; 32 | 33 | constructor(obj: any) { 34 | this.obj = obj; 35 | this.result = []; 36 | } 37 | } 38 | 39 | /* Local Graph Functions 40 | * To build a graph for queries 41 | * To build a graph to perform graph operations on 42 | */ 43 | 44 | export class LocalGraph { 45 | map = new Map(); 46 | 47 | constructor() {} 48 | 49 | add(item: any) { 50 | const uuid = uuidv4(); 51 | this.map.set(uuid, item); 52 | return uuid; 53 | } 54 | 55 | find(uuid: string) { 56 | return this.map.get(uuid); 57 | } 58 | update(uuid: string, item: any) { 59 | this.map.set(uuid, item); 60 | } 61 | } 62 | 63 | export const uuidv4 = () => { 64 | return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxx".replace(/[xy]/g, function (c) { 65 | const r = (Math.random() * 16) | 0, 66 | v = c == "x" ? r : (r & 0x3) | 0x8; 67 | return v.toString(16); 68 | }); 69 | }; 70 | -------------------------------------------------------------------------------- /src/cypher/builder/clause-builder.ts: -------------------------------------------------------------------------------- 1 | import { ICypherStrategy } from ".."; 2 | import { Props } from "../cypher-types"; 3 | import { IQueryBuilder } from "./query-builder"; 4 | import { Handler } from "./handler"; 5 | import { IBuilderMap } from "./map"; 6 | 7 | export interface IClauseBuilder { 8 | queryBuilder: IQueryBuilder; 9 | builderMap: IBuilderMap; 10 | strategy: ICypherStrategy; 11 | } 12 | 13 | export class ClauseBuilder extends Handler { 14 | queryBuilder: IQueryBuilder; 15 | 16 | get strategy() { 17 | return this.queryBuilder.strategy; 18 | } 19 | 20 | get configObj() { 21 | return this.queryBuilder.configObj; 22 | } 23 | 24 | get builderMap() { 25 | return this.queryBuilder.builderMap; 26 | } 27 | 28 | constructor(q: IQueryBuilder) { 29 | super(); 30 | this.queryBuilder = q; 31 | } 32 | 33 | config(config: any) { 34 | return this; 35 | } 36 | 37 | firstFromMap(map: Props) { 38 | return map.values()[0]; 39 | } 40 | 41 | get aliasMap() { 42 | return this.queryBuilder.aliasMap; 43 | } 44 | 45 | error(...msg: any[]) { 46 | console.log(...msg); 47 | } 48 | 49 | mergeAliasMap(aliasMap: Props, name?: string) { 50 | return this.queryBuilder.mergeAliasMap(aliasMap, name); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/cypher/builder/error-handler.ts: -------------------------------------------------------------------------------- 1 | export class ErrorHandler { 2 | error(...msg: any[]) { 3 | console.log(...msg); 4 | throw new Error(msg[0]); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/cypher/builder/handler.ts: -------------------------------------------------------------------------------- 1 | import { ErrorHandler } from "./error-handler"; 2 | 3 | export class Handler { 4 | eh: ErrorHandler; 5 | 6 | // TODO: use IoC injection 7 | constructor() { 8 | this.eh = new ErrorHandler(); 9 | } 10 | 11 | error(...msg: any[]) { 12 | return this.eh.error(...msg); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/cypher/builder/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./return"; 2 | export * from "./read"; 3 | export * from "./write"; 4 | export * from "./load"; 5 | export * from "./query-builder"; 6 | export * from "./handler"; 7 | export * from "./error-handler"; 8 | -------------------------------------------------------------------------------- /src/cypher/builder/load/csv.ts: -------------------------------------------------------------------------------- 1 | import csvtojson from "csvtojson"; 2 | import request from "request"; 3 | import { ClauseBuilder } from "../clause-builder"; 4 | import { Query } from "../.."; 5 | import { IQueryBuilder } from "../query-builder"; 6 | 7 | export class Csv extends ClauseBuilder { 8 | csv: any; 9 | 10 | constructor(q: IQueryBuilder) { 11 | super(q); 12 | this.csv = csvtojson(); 13 | } 14 | 15 | // see https://www.npmjs.com/package/csvtojson 16 | fromFile(url: string, rowAlias: string, headers = true) { 17 | try { 18 | this.csv.fromFile(url).then((json: any) => { 19 | json.forEach((row: any) => { 20 | console.log(row); 21 | }); 22 | this.onComplete(); 23 | }); 24 | } catch (e) { 25 | this.onError(e); 26 | } 27 | } 28 | 29 | fetch(url: string) { 30 | return request.get(url); 31 | } 32 | 33 | fromStream(url: string, rowAlias: string, headers = true) { 34 | try { 35 | const stream = this.fetch(url); 36 | const csv = this.csv.fromStream(stream).subscribe( 37 | (json: any) => { 38 | return new Promise((resolve, reject) => { 39 | // long operation for each json e.g. transform / write into database. 40 | console.log(json); 41 | }); 42 | }, 43 | this.onError, 44 | this.onComplete 45 | ); 46 | } catch (e) { 47 | this.onError(e); 48 | } 49 | } 50 | 51 | onError(err: any) {} 52 | 53 | onComplete() {} 54 | } 55 | -------------------------------------------------------------------------------- /src/cypher/builder/load/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./csv"; 2 | -------------------------------------------------------------------------------- /src/cypher/builder/map.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createAndExprBuilder, 3 | createCreateBuilder, 4 | createDeleteBuilder, 5 | createLabelExprBuilder, 6 | createLimitExprBuilder, 7 | createMatchClauseBuilder, 8 | createNotExprBuilder, 9 | createOrExprBuilder, 10 | createPropExprBuilder, 11 | createReturnCountExprBuilder, 12 | createReturnPropExprBuilder, 13 | createSkipExprBuilder, 14 | createUnionExprBuilder, 15 | createWhereBuilder, 16 | ICreateBuilder, 17 | IDeleteBuilder, 18 | IMatchClauseBuilder, 19 | IQueryBuilder, 20 | IResultExprBuilder, 21 | IReturnExprBuilder, 22 | IWhereClauseBuilder, 23 | WhereExprBuilder, 24 | } from "."; 25 | import { IMatchExprBuilder } from "./read/match/match-expr-builder"; 26 | import { 27 | createResultClauseBuilder, 28 | IResultClauseBuilder, 29 | } from "./read/result/result-clause-builder"; 30 | import { createReturnAggregationExprBuilder } from "./read/return/aggregation-expr-builder"; 31 | import { 32 | createReturnClauseBuilder, 33 | IReturnClauseBuilder, 34 | } from "./read/return/return-clause-builder"; 35 | import { 36 | createWhereSelectAliasExprBuilder, 37 | IWhereSelectAliasExprBuilder, 38 | } from "./read/where/select-alias-expr-builder"; 39 | 40 | export type DeleteClauseBuilderFactoryFn = ( 41 | q: IQueryBuilder, 42 | config: any 43 | ) => IDeleteBuilder; 44 | 45 | export type CreateClauseBuilderFactoryFn = ( 46 | q: IQueryBuilder, 47 | config: any 48 | ) => ICreateBuilder; 49 | 50 | export type MatchClauseBuilderFactoryFn = ( 51 | q: IQueryBuilder, 52 | config: any 53 | ) => IMatchClauseBuilder; 54 | 55 | export type ResultClauseBuilderFactoryFn = ( 56 | q: IQueryBuilder, 57 | config: any 58 | ) => IResultClauseBuilder; 59 | 60 | export type ReturnClauseBuilderFactoryFn = ( 61 | q: IQueryBuilder, 62 | config: any 63 | ) => IReturnClauseBuilder; 64 | 65 | export type MatchExprBuilderFn = ( 66 | cb: IMatchClauseBuilder, 67 | config: any 68 | ) => IMatchExprBuilder; 69 | 70 | export type ResultExprBuilderFn = ( 71 | cb: IResultClauseBuilder, 72 | config: any 73 | ) => IResultExprBuilder; 74 | 75 | export type ReturnExprBuilderFn = ( 76 | cb: IReturnClauseBuilder, 77 | config: any 78 | ) => IReturnExprBuilder; 79 | 80 | export type WhereExprBuilderFn = ( 81 | w: IWhereClauseBuilder, 82 | config: any 83 | ) => WhereExprBuilder; 84 | 85 | export interface ReturnBuilderMap { 86 | root: ReturnClauseBuilderFactoryFn; 87 | count: ReturnExprBuilderFn; 88 | aggregation: ReturnExprBuilderFn; 89 | prop: ReturnExprBuilderFn; 90 | // alias?: ReturnBuilderFn; 91 | } 92 | 93 | export interface ResultBuilderMap { 94 | root: ResultClauseBuilderFactoryFn; 95 | skip: ResultExprBuilderFn; 96 | limit: ResultExprBuilderFn; 97 | // union?: ResultBuilderFn; 98 | } 99 | 100 | export type WhereClauseBuilderFactoryFn = ( 101 | q: IQueryBuilder, 102 | config: any 103 | ) => IWhereClauseBuilder; 104 | 105 | export type WhereSelectAliasBuilderFn = ( 106 | w: IWhereClauseBuilder, 107 | config: any 108 | ) => IWhereSelectAliasExprBuilder; 109 | 110 | export interface WhereBuilderMap { 111 | root: WhereClauseBuilderFactoryFn; 112 | obj: WhereSelectAliasBuilderFn; 113 | or: WhereExprBuilderFn; 114 | and: WhereExprBuilderFn; 115 | not: WhereExprBuilderFn; 116 | label: WhereExprBuilderFn; 117 | prop: WhereExprBuilderFn; 118 | } 119 | 120 | export interface IBuilderMap { 121 | create: { 122 | root: CreateClauseBuilderFactoryFn; 123 | }; 124 | delete: { 125 | root: DeleteClauseBuilderFactoryFn; 126 | }; 127 | match: { 128 | root: MatchClauseBuilderFactoryFn; 129 | }; 130 | where: WhereBuilderMap; 131 | return: ReturnBuilderMap; 132 | result: ResultBuilderMap; 133 | } 134 | 135 | const defaultWhereMap = () => { 136 | return { 137 | root: createWhereBuilder, 138 | obj: createWhereSelectAliasExprBuilder, 139 | or: createOrExprBuilder, 140 | and: createAndExprBuilder, 141 | not: createNotExprBuilder, 142 | label: createLabelExprBuilder, 143 | prop: createPropExprBuilder, 144 | }; 145 | }; 146 | 147 | const defaultResultMap = () => { 148 | return { 149 | root: createResultClauseBuilder, 150 | skip: createSkipExprBuilder, 151 | limit: createLimitExprBuilder, 152 | union: createUnionExprBuilder, 153 | }; 154 | }; 155 | 156 | const defaultReturnMap = () => { 157 | return { 158 | root: createReturnClauseBuilder, 159 | count: createReturnCountExprBuilder, 160 | aggregation: createReturnAggregationExprBuilder, 161 | prop: createReturnPropExprBuilder, 162 | }; 163 | }; 164 | 165 | export const defaultBuilderMap = (): IBuilderMap => { 166 | return { 167 | create: { 168 | root: createCreateBuilder, 169 | }, 170 | delete: { 171 | root: createDeleteBuilder, 172 | }, 173 | match: { 174 | root: createMatchClauseBuilder, 175 | }, 176 | where: defaultWhereMap(), 177 | return: defaultReturnMap(), 178 | result: defaultResultMap(), 179 | }; 180 | }; 181 | -------------------------------------------------------------------------------- /src/cypher/builder/query-builder.ts: -------------------------------------------------------------------------------- 1 | import { Csv } from "./load"; 2 | import { defaultStrategyMap } from "../strategy/defaults"; 3 | import { IStrategyMap } from "../strategy/map"; 4 | import { defaultBuilderMap, IBuilderMap } from "./map"; 5 | import { Props } from "../cypher-types"; 6 | import { ICypherStrategy } from ".."; 7 | 8 | export interface IQueryBuilder { 9 | configObj: any; 10 | aliasMap: Props; 11 | strategy: ICypherStrategy; 12 | builderMap: IBuilderMap; 13 | mergeAliasMap(aliasMap: Props, name?: string): void; 14 | } 15 | 16 | export class QueryBuilder { 17 | strategy: ICypherStrategy; 18 | builderMap: IBuilderMap = defaultBuilderMap(); 19 | aliasMap: Props = {}; 20 | configObj: any; 21 | 22 | constructor(strategy: ICypherStrategy) { 23 | this.strategy = strategy; 24 | } 25 | 26 | config(config: any) { 27 | this.configObj = config; 28 | return this; 29 | } 30 | 31 | mergeAliasMap(aliasMap: Props, name = "alias") { 32 | this.aliasMap[name] = { 33 | ...(this.aliasMap[name] || {}), 34 | ...(aliasMap || {}), 35 | }; 36 | return aliasMap; 37 | } 38 | 39 | get loadCsv(): Csv { 40 | return new Csv(this); 41 | } 42 | 43 | get create(): any { 44 | return this.builderMap.create.root(this, this.configObj); 45 | } 46 | 47 | get delete() { 48 | return this.builderMap.delete.root(this, this.configObj); 49 | } 50 | 51 | get match() { 52 | return this.builderMap.match.root(this, this.configObj); 53 | } 54 | 55 | get where() { 56 | return this.builderMap.where.root(this, this.configObj); 57 | } 58 | 59 | get result() { 60 | return this.builderMap.result.root(this, this.configObj); 61 | } 62 | 63 | get return() { 64 | return this.builderMap.return.root(this, this.configObj); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/cypher/builder/read/expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { Handler, IQueryBuilder } from ".."; 2 | import { ICypherStrategy, IFilterExpr } from "../.."; 3 | import { IClauseBuilder } from "../clause-builder"; 4 | 5 | export interface IExprBuilder { 6 | exprName: string; 7 | clauseName: string; 8 | q: IQueryBuilder; 9 | clauseBuilder?: IClauseBuilder; 10 | expr?: IFilterExpr; 11 | strategy: ICypherStrategy; 12 | config(config: any): IExprBuilder; 13 | setExpression(config: any): IExprBuilder; 14 | addAsExpression(name: string, config: any): IExprBuilder; 15 | } 16 | 17 | export class ExprBuilder extends Handler implements IExprBuilder { 18 | q: IQueryBuilder; 19 | clauseBuilder?: IClauseBuilder; 20 | exprName: string = ""; 21 | clauseName: string = ""; 22 | expr?: IFilterExpr; 23 | configObj: any; 24 | 25 | constructor(q: IQueryBuilder) { 26 | super(); 27 | this.q = q; 28 | } 29 | 30 | get strategy() { 31 | return this.q.strategy; 32 | } 33 | 34 | config(config: any) { 35 | this.configObj = config; 36 | return this; 37 | } 38 | 39 | setExpression(config: any = {}) { 40 | // Note: the create and add could both be encapsulated under the addExpression method 41 | // createExpression uses strategyMap 42 | this.addAsExpression(this.exprName, config); 43 | const expr = this.strategy.latestExpr; 44 | if (!expr) return this; 45 | // based on the expr figures out which controller and clause to add it to 46 | this.expr = expr; 47 | return this; 48 | } 49 | 50 | addAsExpression(name: string, config: any) { 51 | // Note: the create and add could both be encapsulated under the addExpression method 52 | // createExpression uses strategyMap 53 | this.strategy.addAsExpression(this.clauseName, name, config); 54 | return this; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/cypher/builder/read/generic/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./number-expr-builder"; 2 | -------------------------------------------------------------------------------- /src/cypher/builder/read/generic/number-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IClauseBuilder } from "../../clause-builder"; 2 | import { ExprBuilder, IExprBuilder } from "../expr-builder"; 3 | 4 | export interface INumberExprBuilder extends IExprBuilder { 5 | number(num: number): INumberExprBuilder; 6 | } 7 | 8 | export class NumberExprBuilder extends ExprBuilder { 9 | constructor(clauseBuilder: IClauseBuilder) { 10 | super(clauseBuilder.queryBuilder); 11 | this.clauseBuilder = clauseBuilder; 12 | } 13 | 14 | protected isValidNumber(num: number) { 15 | return num && num >= 0; 16 | } 17 | 18 | number(num: number) { 19 | if (!this.isValidNumber(num)) { 20 | this.error("Invalid number"); 21 | return; 22 | } 23 | return this; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/cypher/builder/read/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./match"; 2 | export * from "./where"; 3 | export * from "./return"; 4 | export * from "./result"; 5 | -------------------------------------------------------------------------------- /src/cypher/builder/read/match/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./match-obj-expr-builder"; 2 | export * from "./match-clause-builder"; 3 | -------------------------------------------------------------------------------- /src/cypher/builder/read/match/match-clause-builder.ts: -------------------------------------------------------------------------------- 1 | import { ClauseBuilder } from "../../clause-builder"; 2 | import { IQueryBuilder } from "../.."; 3 | import { 4 | IMatchObjExprBuilder, 5 | MatchObjExprBuilder, 6 | } from "./match-obj-expr-builder"; 7 | 8 | export const createMatchClauseBuilder = (q: IQueryBuilder, config: any) => 9 | new MatchClauseBuilder(q).config(config); 10 | 11 | export interface IMatchClauseBuilder { 12 | obj(alias: string): IMatchObjExprBuilder; 13 | } 14 | 15 | export class MatchClauseBuilder extends ClauseBuilder { 16 | obj(alias: string = "_") { 17 | return new MatchObjExprBuilder(this.queryBuilder).config({ alias }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/cypher/builder/read/match/match-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IExprBuilder } from "../expr-builder"; 2 | 3 | export interface IMatchExprBuilder extends IExprBuilder {} 4 | -------------------------------------------------------------------------------- /src/cypher/builder/read/match/match-obj-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IQueryBuilder } from "../.."; 2 | import { Props } from "../../../cypher-types"; 3 | import { ClauseBuilder } from "../../clause-builder"; 4 | import { ExprBuilder } from "../expr-builder"; 5 | 6 | export interface MatchObjConfig { 7 | alias?: string; 8 | labels?: string[]; 9 | props?: Props; 10 | } 11 | 12 | export interface IMatchObjExprBuilder { 13 | matches(config: MatchObjConfig): any; 14 | } 15 | 16 | export const createMatchObjExprBuilder = (q: IQueryBuilder, config: any) => 17 | new MatchObjExprBuilder(q).config(config); 18 | 19 | export class MatchObjExprBuilder 20 | extends ExprBuilder 21 | implements IMatchObjExprBuilder 22 | { 23 | alias: string = "_"; 24 | $optional: boolean = false; 25 | exprName: string = "obj"; 26 | 27 | config(config: MatchObjConfig) { 28 | this.setAlias(config.alias); 29 | return super.config(config); 30 | } 31 | 32 | optional() { 33 | this.$optional = true; 34 | return this; 35 | } 36 | 37 | protected setAlias(alias: string = "_") { 38 | this.alias = alias; 39 | return this; 40 | } 41 | 42 | matches(config: MatchObjConfig) { 43 | const { alias } = this; 44 | return {}; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/cypher/builder/read/result/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./limit-expr-builder"; 2 | export * from "./order-by-expr-builder"; 3 | export * from "./result-number-expr-builder"; 4 | export * from "./skip-expr-builder"; 5 | export * from "./union-expr-builder"; 6 | export * from "./result-expr-builder"; 7 | -------------------------------------------------------------------------------- /src/cypher/builder/read/result/limit-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IQueryBuilder } from "../.."; 2 | import { IClauseBuilder } from "../../clause-builder"; 3 | import { IResultClauseBuilder } from "./result-clause-builder"; 4 | import { IResultNumberExprBuilder } from "./result-number-expr-builder"; 5 | import { ResultNumberExprBuilder } from "./result-number-expr-builder"; 6 | 7 | export const createLimitExprBuilder = (cb: IResultClauseBuilder, config: any) => 8 | new LimitExprBuilder(cb).config(config); 9 | 10 | export interface ILimitExprBuilder extends IResultNumberExprBuilder {} 11 | 12 | export class LimitExprBuilder extends ResultNumberExprBuilder { 13 | exprName: string = "limit"; 14 | } 15 | -------------------------------------------------------------------------------- /src/cypher/builder/read/result/order-by-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { ResultExprBuilder } from "../.."; 2 | import { IResultClauseBuilder } from "./result-clause-builder"; 3 | 4 | export const createOrderByBuilder = (cb: IResultClauseBuilder, config: any) => 5 | new OrderByExprBuilder(cb).config(config); 6 | 7 | export class OrderByExprBuilder extends ResultExprBuilder { 8 | exprName: string = "orderBy"; 9 | } 10 | -------------------------------------------------------------------------------- /src/cypher/builder/read/result/result-clause-builder.ts: -------------------------------------------------------------------------------- 1 | import { IResultExprBuilder } from "."; 2 | import { IQueryBuilder } from "../.."; 3 | import { ClauseBuilder, IClauseBuilder } from "../../clause-builder"; 4 | 5 | export const createResultClauseBuilder = (q: IQueryBuilder, config: any) => 6 | new ResultClauseBuilder(q).config(config); 7 | 8 | export interface IResultClauseBuilder extends IClauseBuilder { 9 | // obj(alias: string): IResultExprBuilder; 10 | } 11 | 12 | export class ResultClauseBuilder 13 | extends ClauseBuilder 14 | implements IResultClauseBuilder {} 15 | -------------------------------------------------------------------------------- /src/cypher/builder/read/result/result-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IClauseBuilder } from "../../clause-builder"; 2 | import { ExprBuilder } from "../expr-builder"; 3 | 4 | export interface IResultExprBuilder {} 5 | 6 | export class ResultExprBuilder extends ExprBuilder { 7 | clauseBuilder: IClauseBuilder; 8 | clauseName: string = "result"; 9 | 10 | constructor(clauseBuilder: IClauseBuilder) { 11 | super(clauseBuilder.queryBuilder); 12 | this.clauseBuilder = clauseBuilder; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/cypher/builder/read/result/result-number-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IResultExprBuilder } from "."; 2 | import { IStrategyResult } from "../../.."; 3 | import { NumberExprBuilder } from "../generic"; 4 | 5 | export interface IResultNumberExprBuilder extends IResultExprBuilder { 6 | number(num: number): IResultNumberExprBuilder; 7 | } 8 | 9 | export class ResultNumberExprBuilder extends NumberExprBuilder { 10 | result?: IStrategyResult; 11 | } 12 | -------------------------------------------------------------------------------- /src/cypher/builder/read/result/skip-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IResultNumberExprBuilder, 3 | ResultNumberExprBuilder, 4 | } from "./result-number-expr-builder"; 5 | import { IResultClauseBuilder } from "./result-clause-builder"; 6 | 7 | export const createSkipExprBuilder = (cb: IResultClauseBuilder, config: any) => 8 | new SkipExprBuilder(cb).config(config); 9 | 10 | export interface ISkipExprBuilder extends IResultNumberExprBuilder {} 11 | 12 | export class SkipExprBuilder extends ResultNumberExprBuilder { 13 | exprName: string = "skip"; 14 | } 15 | -------------------------------------------------------------------------------- /src/cypher/builder/read/result/union-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IResultExprBuilder, ResultExprBuilder } from "."; 2 | import { IResultClauseBuilder } from "./result-clause-builder"; 3 | 4 | export const createUnionExprBuilder = (cb: IResultClauseBuilder, config: any) => 5 | new UnionExprBuilder(cb).config(config); 6 | 7 | export interface IUnionExprBuilder extends IResultExprBuilder {} 8 | 9 | export class UnionExprBuilder extends ResultExprBuilder { 10 | exprName: string = "union"; 11 | } 12 | -------------------------------------------------------------------------------- /src/cypher/builder/read/return/aggregation-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IReturnClauseBuilder, IReturnExprBuilder } from "."; 2 | import { NumberExprBuilder } from "../generic"; 3 | 4 | export const createReturnAggregationExprBuilder = ( 5 | cb: IReturnClauseBuilder, 6 | config: any 7 | ): IReturnExprBuilder => new AggregationExprBuilder(cb).config(config); 8 | 9 | export interface IAggregationExprBuilder extends IReturnExprBuilder { 10 | sum(name: string): IAggregationExprBuilder; 11 | avg(name: string): IAggregationExprBuilder; 12 | } 13 | 14 | export class AggregationExprBuilder 15 | extends NumberExprBuilder 16 | implements IAggregationExprBuilder 17 | { 18 | $distinct = false; 19 | 20 | sum(name: string) { 21 | return this; 22 | } 23 | 24 | avg(name: string) { 25 | return this; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/cypher/builder/read/return/count-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IReturnClauseBuilder, IReturnExprBuilder } from "."; 2 | import { NumberExprBuilder } from "../generic"; 3 | 4 | export const createReturnCountExprBuilder = ( 5 | cb: IReturnClauseBuilder, 6 | config: any 7 | ): IReturnExprBuilder => new CountExprBuilder(cb).config(config); 8 | 9 | export interface ICountExprBuilder extends IReturnExprBuilder {} 10 | 11 | export class CountExprBuilder 12 | extends NumberExprBuilder 13 | implements ICountExprBuilder 14 | { 15 | $distinct = false; 16 | 17 | distinct() { 18 | this.$distinct = true; 19 | return this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/cypher/builder/read/return/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./aggregation-expr-builder"; 2 | export * from "./count-expr-builder"; 3 | export * from "./prop-expr-builder"; 4 | export * from "./select-alias-expr-builder"; 5 | export * from "./return-clause-builder"; 6 | export * from "./return-expr-builder"; 7 | -------------------------------------------------------------------------------- /src/cypher/builder/read/return/prop-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IReturnExprBuilder, ReturnExprBuilder } from "."; 2 | import { IQueryBuilder } from "../.."; 3 | import { IResultClauseBuilder } from "../result/result-clause-builder"; 4 | 5 | export interface IReturnPropExprBuilder extends IReturnExprBuilder { 6 | // matches(expr: any): any; 7 | } 8 | 9 | export const createReturnPropExprBuilder = ( 10 | cb: IResultClauseBuilder, 11 | config: any 12 | ) => new PropExprBuilder(cb).config(config); 13 | 14 | export class PropExprBuilder 15 | extends ReturnExprBuilder 16 | implements IReturnPropExprBuilder 17 | { 18 | exprName: string = "or"; 19 | } 20 | -------------------------------------------------------------------------------- /src/cypher/builder/read/return/return-clause-builder.ts: -------------------------------------------------------------------------------- 1 | import { IReturnExprBuilder } from "."; 2 | import { IQueryBuilder } from "../.."; 3 | import { ClauseBuilder, IClauseBuilder } from "../../clause-builder"; 4 | 5 | export const createReturnClauseBuilder = (q: IQueryBuilder, config: any) => 6 | new ReturnClauseBuilder(q).config(config); 7 | 8 | export interface IReturnClauseBuilder extends IClauseBuilder { 9 | aggregate(config: any): IReturnExprBuilder; 10 | } 11 | 12 | export class ReturnClauseBuilder 13 | extends ClauseBuilder 14 | implements IReturnClauseBuilder 15 | { 16 | aggregate(config: any = {}): IReturnExprBuilder { 17 | return this.builderMap.return.aggregation(this, config); 18 | } 19 | 20 | prop(name: string): IReturnExprBuilder { 21 | return this.builderMap.return.prop(this, { name }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/cypher/builder/read/return/return-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IClauseBuilder } from "../../clause-builder"; 2 | import { ReturnBuilderMap } from "../../map"; 3 | import { ExprBuilder } from "../expr-builder"; 4 | 5 | export interface IReturnExprBuilder {} 6 | 7 | export class ReturnExprBuilder extends ExprBuilder { 8 | clauseBuilder: IClauseBuilder; 9 | clauseName: string = "return"; 10 | 11 | constructor(clauseBuilder: IClauseBuilder) { 12 | super(clauseBuilder.queryBuilder); 13 | this.clauseBuilder = clauseBuilder; 14 | } 15 | 16 | get return(): ReturnBuilderMap { 17 | return this.clauseBuilder.builderMap.return; 18 | } 19 | 20 | returns(expr: any) { 21 | return this; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/cypher/builder/read/return/select-alias-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IReturnClauseBuilder, 3 | IReturnPropExprBuilder, 4 | ReturnExprBuilder, 5 | } from "."; 6 | import { IExprBuilder } from "../expr-builder"; 7 | 8 | export interface IReturnSelectAliasExprBuilder { 9 | get prop(): IReturnPropExprBuilder; 10 | } 11 | 12 | export const createWhereSelectAliasExprBuilder = ( 13 | cb: IReturnClauseBuilder, 14 | config: any 15 | ) => new ReturnSelectAliasExprBuilder(cb).config(config); 16 | 17 | export class ReturnSelectAliasExprBuilder extends ReturnExprBuilder { 18 | protected mapped(name: string): IExprBuilder { 19 | return (this.return as any)[name](this.clauseBuilder, this.configObj); 20 | } 21 | 22 | get prop(): IReturnPropExprBuilder { 23 | return this.mapped("prop") as IReturnPropExprBuilder; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/boolean/and-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IWhereClauseBuilder } from ".."; 2 | import { IFilterExpr } from "../../../.."; 3 | import { 4 | BooleanExprBuilder, 5 | IBooleanExprBuilder, 6 | } from "./boolean-expr-builder"; 7 | 8 | export interface IAndExprBuilder extends IBooleanExprBuilder {} 9 | 10 | export const createAndExprBuilder = (w: IWhereClauseBuilder, config: any) => 11 | new AndExprBuilder(w, config); 12 | 13 | export class AndExprBuilder extends BooleanExprBuilder { 14 | exprName: string = "and"; 15 | 16 | constructor(w: IWhereClauseBuilder, config: any = {}) { 17 | super(w, config); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/boolean/boolean-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IWhereClauseBuilder } from ".."; 2 | import { IWhereExprBuilder, WhereExprBuilder } from "../where-expr-builder"; 3 | 4 | export interface IBooleanExprBuilder extends IWhereExprBuilder {} 5 | 6 | export class BooleanExprBuilder extends WhereExprBuilder { 7 | constructor(whereClauseBuilder: IWhereClauseBuilder, config: any) { 8 | super(whereClauseBuilder); 9 | this.setExpression(config); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/boolean/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./and-expr-builder"; 2 | export * from "./not-expr-builder"; 3 | export * from "./or-expr-builder"; 4 | export * from "./boolean-expr-builder"; 5 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/boolean/not-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IWhereClauseBuilder } from ".."; 2 | import { 3 | BooleanExprBuilder, 4 | IBooleanExprBuilder, 5 | } from "./boolean-expr-builder"; 6 | 7 | export interface INotExprBuilder extends IBooleanExprBuilder {} 8 | 9 | export const createNotExprBuilder = (w: IWhereClauseBuilder, config: any) => 10 | new NotExprBuilder(w, config); 11 | 12 | export class NotExprBuilder extends BooleanExprBuilder { 13 | exprName: string = "not"; 14 | } 15 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/boolean/or-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { BooleanExprBuilder, IBooleanExprBuilder } from "."; 2 | import { IWhereClauseBuilder } from ".."; 3 | 4 | export interface IOrExprBuilder extends IBooleanExprBuilder { 5 | matches(expr: any): any; 6 | } 7 | 8 | export const createOrExprBuilder = (w: IWhereClauseBuilder, config: any) => 9 | new OrExprBuilder(w, config); 10 | 11 | export class OrExprBuilder extends BooleanExprBuilder { 12 | exprName: string = "or"; 13 | } 14 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./where-clause-builder"; 2 | export * from "./where-expr-builder"; 3 | export * from "./prop-expr-builder"; 4 | export * from "./label-expr-builder"; 5 | export * from "./boolean"; 6 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/label-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IWhereClauseBuilder, IWhereExprBuilder, WhereExprBuilder } from ".."; 2 | 3 | export interface ILabelExprBuilder extends IWhereExprBuilder { 4 | matches(expr: any): any; 5 | } 6 | 7 | export const createLabelExprBuilder = (w: IWhereClauseBuilder, config: any) => 8 | new LabelExprBuilder(w).config(config); 9 | 10 | export class LabelExprBuilder extends WhereExprBuilder { 11 | exprName: string = "label"; 12 | } 13 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/prop-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IWhereClauseBuilder, IWhereExprBuilder, WhereExprBuilder } from ".."; 2 | 3 | export interface IPropExprBuilder extends IWhereExprBuilder { 4 | matches(expr: any): any; 5 | } 6 | 7 | export const createPropExprBuilder = (w: IWhereClauseBuilder, config: any) => 8 | new PropExprBuilder(w).config(config); 9 | 10 | export class PropExprBuilder extends WhereExprBuilder { 11 | exprName: string = "prop"; 12 | } 13 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/select-alias-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IAndExprBuilder, 3 | INotExprBuilder, 4 | IOrExprBuilder, 5 | IWhereClauseBuilder, 6 | WhereExprBuilder, 7 | } from "."; 8 | import { IExprBuilder } from "../expr-builder"; 9 | 10 | export interface IWhereSelectAliasExprBuilder { 11 | get or(): IOrExprBuilder; 12 | get and(): IAndExprBuilder; 13 | get not(): INotExprBuilder; 14 | } 15 | 16 | export const createWhereSelectAliasExprBuilder = ( 17 | w: IWhereClauseBuilder, 18 | config: any 19 | ) => new WhereSelectAliasExprBuilder(w).config(config); 20 | 21 | export class WhereSelectAliasExprBuilder extends WhereExprBuilder { 22 | protected mapped(name: string): IExprBuilder { 23 | return (this.where as any)[name](this.clauseBuilder, this.configObj); 24 | } 25 | 26 | get or(): IOrExprBuilder { 27 | return this.mapped("or") as IOrExprBuilder; 28 | } 29 | 30 | get and(): IAndExprBuilder { 31 | return this.mapped("and") as IAndExprBuilder; 32 | } 33 | 34 | get not(): INotExprBuilder { 35 | return this.mapped("not") as INotExprBuilder; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/where-clause-builder.ts: -------------------------------------------------------------------------------- 1 | import { IQueryBuilder } from "../.."; 2 | import { ClauseBuilder, IClauseBuilder } from "../../clause-builder"; 3 | import { WhereBuilderMap } from "../../map"; 4 | import { IOrExprBuilder, IAndExprBuilder, INotExprBuilder } from "./boolean"; 5 | import { IWhereSelectAliasExprBuilder } from "./select-alias-expr-builder"; 6 | 7 | export type NodeMatchFn = (node: any) => boolean; 8 | 9 | export const createWhereBuilder = (q: IQueryBuilder, config: any) => 10 | new WhereClauseBuilder(q).config(config); 11 | 12 | export interface IWhereClauseBuilder extends IClauseBuilder { 13 | get where(): WhereBuilderMap; 14 | } 15 | 16 | export class WhereClauseBuilder 17 | extends ClauseBuilder 18 | implements IWhereClauseBuilder 19 | { 20 | nodeMatches(fn: NodeMatchFn) {} 21 | 22 | get where() { 23 | return this.builderMap.where; 24 | } 25 | 26 | obj(alias: string): IWhereSelectAliasExprBuilder { 27 | return this.where.obj(this, alias); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/cypher/builder/read/where/where-expr-builder.ts: -------------------------------------------------------------------------------- 1 | import { IWhereClauseBuilder } from "."; 2 | import { IClauseBuilder } from "../../clause-builder"; 3 | import { WhereBuilderMap } from "../../map"; 4 | import { ExprBuilder, IExprBuilder } from "../expr-builder"; 5 | 6 | export interface IWhereExprBuilder extends IExprBuilder { 7 | clauseBuilder: IClauseBuilder; 8 | matches(expr: any): any; 9 | } 10 | 11 | export class WhereExprBuilder extends ExprBuilder implements IWhereExprBuilder { 12 | clauseBuilder: IWhereClauseBuilder; 13 | clauseName: string = "where"; 14 | 15 | constructor(clauseBuilder: IWhereClauseBuilder) { 16 | super(clauseBuilder.queryBuilder); 17 | this.clauseBuilder = clauseBuilder; 18 | } 19 | 20 | get where(): WhereBuilderMap { 21 | return this.clauseBuilder.builderMap.where; 22 | } 23 | 24 | matches(expr: any) { 25 | return this; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/cypher/builder/return.ts: -------------------------------------------------------------------------------- 1 | import { ClauseBuilder } from "./clause-builder"; 2 | 3 | export class Return extends ClauseBuilder { 4 | node(alias: string, prop?: string) { 5 | const node = this.aliasMap["matches"][alias]; 6 | if (node.__type !== "node") { 7 | this.error(`${alias} is not a node`); 8 | } 9 | return prop ? node[prop] : node; 10 | } 11 | 12 | rel(alias: string, prop: string) { 13 | const rel = this.aliasMap["matches"][alias]; 14 | if (rel.__type !== "edge") { 15 | this.error(`${alias} is not an edge`); 16 | } 17 | return prop ? rel[prop] : rel; 18 | } 19 | 20 | countNodes(alias: string, distinct: boolean) {} 21 | 22 | countEdges(alias: string, distinct: boolean) {} 23 | 24 | count(expr: any, distinct: boolean) {} 25 | 26 | // count(*) 27 | countAll() {} 28 | 29 | min(expr: any) {} 30 | 31 | max(expr: any) {} 32 | 33 | // numerical 34 | sum(expr: any) {} 35 | 36 | // numerical 37 | avg(expr: any) {} 38 | } 39 | -------------------------------------------------------------------------------- /src/cypher/builder/write/constraint.ts: -------------------------------------------------------------------------------- 1 | import { ClauseBuilder } from "../clause-builder"; 2 | 3 | export class Constraint extends ClauseBuilder { 4 | nodePropUnique(label: string, propName: string) {} 5 | 6 | relationPropUnique(label: string, propName: string) {} 7 | } 8 | -------------------------------------------------------------------------------- /src/cypher/builder/write/create.ts: -------------------------------------------------------------------------------- 1 | import { IQueryBuilder } from ".."; 2 | import { 3 | DirectedRelationDef, 4 | NodeDef, 5 | RelationDef, 6 | StrMap, 7 | } from "../../cypher-types"; 8 | import { ClauseBuilder } from "../clause-builder"; 9 | 10 | export interface ICreateBuilder {} 11 | 12 | export const createCreateBuilder = ( 13 | q: IQueryBuilder, 14 | config: any 15 | ): ICreateBuilder => { 16 | return new CreateBuilder(q).config(config); 17 | }; 18 | 19 | export class CreateBuilder extends ClauseBuilder { 20 | relation(fromNode: NodeDef, relation: DirectedRelationDef, toNode: NodeDef) { 21 | // const from = this.firstFromMap(this.node(fromNode)); 22 | // const to = this.firstFromMap(this.node(toNode)); 23 | // const map = this.ctx.createRel(from, relation, to); 24 | return {}; 25 | } 26 | 27 | relationTo(fromNode: NodeDef, relation: RelationDef, toNode: NodeDef) { 28 | return this.relation(fromNode, { ...relation, direction: "to" }, toNode); 29 | } 30 | 31 | relationFrom(fromNode: NodeDef, relation: RelationDef, toNode: NodeDef) { 32 | return this.relation(fromNode, { ...relation, direction: "from" }, toNode); 33 | } 34 | 35 | // CREATE (n:Person:Swedish) 36 | // https://neo4j.com/docs/cypher-manual/current/clauses/create/#create-create-a-node-with-multiple-labels 37 | node(opts: NodeDef = {}, merge = true) { 38 | // const node = this.ctx.createNode(opts); 39 | // if (!opts.alias) 40 | // return { 41 | // _: node, 42 | // }; 43 | // const map = { 44 | // [opts.alias]: node, 45 | // }; 46 | return {}; 47 | // if (!merge) return map; 48 | // return this.mergeAliasMap(map); 49 | } 50 | 51 | // CREATE (n:Person:Swedish), (m:Person:Danish) 52 | // https://neo4j.com/docs/cypher-manual/current/clauses/create/#create-create-multiple-nodes 53 | nodes(nodeDefs: NodeDef[]) { 54 | const map = nodeDefs.reduce((acc, nodeDef) => { 55 | const mapping = this.node(nodeDef, false); 56 | acc = { 57 | ...mapping, 58 | }; 59 | return acc; 60 | }, {}); 61 | return this.mergeAliasMap(map); 62 | } 63 | 64 | relatednodes( 65 | nodeDefs: NodeDef[], 66 | relationShipMap: StrMap, 67 | skipOnMissingKey = false 68 | ) { 69 | const map = this.nodes(nodeDefs); 70 | const nodeKeys = nodeDefs.map((nodeDef) => nodeDef.alias); 71 | const resultMap = Object.keys(relationShipMap).reduce((acc, key) => { 72 | const targetKey = relationShipMap[key]; 73 | if (!nodeKeys.includes(key)) { 74 | } 75 | if (!nodeKeys.includes(targetKey)) { 76 | } 77 | // ... 78 | return acc; 79 | }); 80 | return resultMap; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/cypher/builder/write/delete.ts: -------------------------------------------------------------------------------- 1 | import { IQueryBuilder } from ".."; 2 | import { ClauseBuilder } from "../clause-builder"; 3 | 4 | export interface IDeleteBuilder { 5 | nodes(...labels: string[]): any; 6 | relations(...labels: string[]): any; 7 | } 8 | 9 | export const createDeleteBuilder = ( 10 | q: IQueryBuilder, 11 | config: any 12 | ): IDeleteBuilder => { 13 | return new DeleteBuilder(q).config(config); 14 | }; 15 | 16 | export class DeleteBuilder extends ClauseBuilder { 17 | nodes(...labels: string[]) {} 18 | relations(...labels: string[]) {} 19 | } 20 | -------------------------------------------------------------------------------- /src/cypher/builder/write/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create"; 2 | export * from "./delete"; 3 | export * from "./merge"; 4 | export * from "./set"; 5 | -------------------------------------------------------------------------------- /src/cypher/builder/write/merge/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./merge"; 2 | export * from "./on-create-set"; 3 | export * from "./on-match-set"; 4 | -------------------------------------------------------------------------------- /src/cypher/builder/write/merge/merge.ts: -------------------------------------------------------------------------------- 1 | import { ClauseBuilder } from "../../clause-builder"; 2 | 3 | export class Merge extends ClauseBuilder {} 4 | -------------------------------------------------------------------------------- /src/cypher/builder/write/merge/on-create-set.ts: -------------------------------------------------------------------------------- 1 | import { ClauseBuilder } from "../../clause-builder"; 2 | 3 | export class OnCreateSet extends ClauseBuilder {} 4 | -------------------------------------------------------------------------------- /src/cypher/builder/write/merge/on-match-set.ts: -------------------------------------------------------------------------------- 1 | import { ClauseBuilder } from "../../clause-builder"; 2 | 3 | export class OnMatchSet extends ClauseBuilder {} 4 | -------------------------------------------------------------------------------- /src/cypher/builder/write/set.ts: -------------------------------------------------------------------------------- 1 | import { ClauseBuilder } from "../clause-builder"; 2 | 3 | export class SetObj extends ClauseBuilder {} 4 | -------------------------------------------------------------------------------- /src/cypher/cypher-types.ts: -------------------------------------------------------------------------------- 1 | import { IFilterResult } from "."; 2 | import { IStrategyResult } from ".."; 3 | 4 | export interface IQueryResult { 5 | headers: string[]; 6 | data: any[]; 7 | rows: number; 8 | columns: number; 9 | } 10 | 11 | export interface AliasMap { 12 | [alias: string]: NodeDef; 13 | } 14 | 15 | export interface Props { 16 | [key: string]: any; 17 | } 18 | 19 | export interface StrMap { 20 | [key: string]: string; 21 | } 22 | 23 | export type RelSetArgs = { 24 | direction?: "from" | "to"; 25 | }; 26 | 27 | export type NodeRelOpts = { 28 | from?: NodeDef; 29 | to?: NodeDef; 30 | relation?: RelationDef; 31 | }; 32 | 33 | export type GraphObjDef = { 34 | [key: string]: any; 35 | type?: string; 36 | alias?: string; 37 | labels?: string[]; 38 | label?: string; 39 | props?: Props; 40 | }; 41 | 42 | export interface NodeDef extends GraphObjDef { 43 | type?: "node"; 44 | } 45 | 46 | export interface RelationDef extends GraphObjDef { 47 | type?: "edge"; 48 | } 49 | 50 | export interface DirectedRelationDef extends RelationDef { 51 | direction?: "from" | "to"; 52 | } 53 | -------------------------------------------------------------------------------- /src/cypher/executer/executer.ts: -------------------------------------------------------------------------------- 1 | import { ICypherStrategy } from ".."; 2 | 3 | export interface ICypherExecuter { 4 | strategy: ICypherStrategy; 5 | configureStrategy(config: any): ICypherExecuter; 6 | run(): any; 7 | } 8 | 9 | export class CypherStrategyExecuter { 10 | strategy: ICypherStrategy; 11 | 12 | constructor(strategy: ICypherStrategy) { 13 | this.strategy = strategy; 14 | } 15 | 16 | configure(config: any) { 17 | this.strategy.configure(config); 18 | return this; 19 | } 20 | 21 | run() { 22 | return this.strategy.run(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/cypher/executer/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./executer"; 2 | -------------------------------------------------------------------------------- /src/cypher/executer/query-executer.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategyExecuter } from "."; 2 | 3 | // Run query operations on graph (traverse and read) 4 | export class QueryStrategyExecuter extends CypherStrategyExecuter {} 5 | -------------------------------------------------------------------------------- /src/cypher/executer/transaction-executer.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategyExecuter } from "."; 2 | 3 | // Run create, merge and delete operations on graph (mutations) 4 | export class TransactionStrategyExecuter extends CypherStrategyExecuter {} 5 | -------------------------------------------------------------------------------- /src/cypher/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./builder"; 2 | export * from "./strategy"; 3 | export * from "./executer"; 4 | export * from "./query"; 5 | -------------------------------------------------------------------------------- /src/cypher/query.ts: -------------------------------------------------------------------------------- 1 | import { GunAPI } from ".."; 2 | 3 | export const query = (ctx: GunAPI) => new Query(ctx); 4 | 5 | export class Query { 6 | ctx: GunAPI; 7 | results: any[] = []; 8 | 9 | constructor(ctx: GunAPI) { 10 | this.ctx = ctx; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./match-clause"; 2 | export * from "./match-clauses"; 3 | export * from "./query-clause"; 4 | export * from "./query-clauses"; 5 | export * from "./return-clause"; 6 | export * from "./return-clauses"; 7 | export * from "./result-clause"; 8 | export * from "./result-clauses"; 9 | export * from "./where-clause"; 10 | export * from "./where-clauses"; 11 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/match-clause.ts: -------------------------------------------------------------------------------- 1 | import { QueryClause } from "."; 2 | import { ClauseType } from "../enum"; 3 | import { IQueryClause } from "./query-clause"; 4 | 5 | export interface IMatchClause extends IQueryClause {} 6 | 7 | export class MatchClause extends QueryClause { 8 | get type(): ClauseType { 9 | return ClauseType.where; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/match-clauses.ts: -------------------------------------------------------------------------------- 1 | import { MatchController } from ".."; 2 | import { ClauseType } from "../enum"; 3 | import { MatchClause } from "./match-clause"; 4 | import { IQueryClauses, QueryClauses } from "./query-clauses"; 5 | 6 | export interface IMatchClauses extends IQueryClauses { 7 | addClause(clause: MatchClause): IMatchClauses; 8 | } 9 | 10 | export class MatchClauses extends QueryClauses implements IMatchClauses { 11 | controller?: MatchController; 12 | 13 | get map() { 14 | return this.controller?.map; 15 | } 16 | 17 | addClause(clause: MatchClause) { 18 | return super.addClause(clause); 19 | } 20 | 21 | isValid(clause: MatchClause) { 22 | return clause.type === ClauseType.match; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/query-clause.ts: -------------------------------------------------------------------------------- 1 | import { IQueryClauses } from "."; 2 | import { IAliasFilterExpr, IFilterExpr } from ".."; 3 | import { ClauseType, WhereFilterType } from "../enum"; 4 | import { StrategyHandler } from "../strategy-handler"; 5 | 6 | export interface IQueryClause { 7 | subtype: WhereFilterType; 8 | type: ClauseType; 9 | current: IFilterExpr; 10 | latestExpr: IFilterExpr; 11 | 12 | setContainer(container: IQueryClauses): IQueryClause; 13 | createExpression(key: string, config: any): IFilterExpr | undefined; 14 | addAsExpression(key: string, config: any): IQueryClause; 15 | addExpressions(...expressions: IFilterExpr[]): IQueryClause; 16 | setAliasFilterExpr(aliasFilterExpr: IAliasFilterExpr): IQueryClause; 17 | findMatchingMapKey(key: string): any; 18 | findExprMapForKey(key: string): any; 19 | } 20 | 21 | export class QueryClause extends StrategyHandler implements IQueryClause { 22 | container?: IQueryClauses; 23 | expressions: IFilterExpr[] = []; 24 | aliasFilterExpr?: IAliasFilterExpr; 25 | 26 | get exprMapKeys(): string[] { 27 | return []; 28 | } 29 | 30 | get map() { 31 | return this.container?.map; 32 | } 33 | 34 | get current(): IFilterExpr { 35 | return this.expressions[this.expressions.length - 1]; 36 | } 37 | 38 | get latestExpr(): IFilterExpr { 39 | return this.current; 40 | } 41 | 42 | setContainer(container: IQueryClauses) { 43 | this.container = container; 44 | return this; 45 | } 46 | 47 | addAsExpression(key: string, config: any): IQueryClause { 48 | const expr = this.createExpression(key, config); 49 | expr && this.addExpressions(expr); 50 | return this; 51 | } 52 | 53 | findMatchingMapKey(key: string): string | undefined { 54 | return this.exprMapKeys.find( 55 | (item: string) => this.map[item] && this.map[item][key] 56 | ); 57 | } 58 | 59 | findExprMapForKey(key: string): any { 60 | const matchingMapKey = this.findMatchingMapKey(key); 61 | return matchingMapKey ? this.map[matchingMapKey] : this.map; 62 | } 63 | 64 | createExpression(key: string, config: any): IFilterExpr | undefined { 65 | const exprMap = this.findExprMapForKey(key); 66 | const createExprFn = exprMap[key]; 67 | if (!createExprFn) { 68 | this.error(`No matching expression found for ${key}`); 69 | return; 70 | } 71 | return createExprFn(config); 72 | } 73 | 74 | addExpressions(...expressions: IFilterExpr[]) { 75 | this.expressions.push(...expressions); 76 | return this; 77 | } 78 | 79 | setAliasFilterExpr(aliasFilterExpr: IAliasFilterExpr) { 80 | this.aliasFilterExpr = aliasFilterExpr; 81 | return this; 82 | } 83 | 84 | subtype: WhereFilterType = WhereFilterType.none; 85 | 86 | error(msg: string) { 87 | throw new Error(msg); 88 | } 89 | 90 | get typeName(): string { 91 | return ClauseType[this.type]; 92 | } 93 | 94 | get type() { 95 | this.error("Must be implemented by subclass"); 96 | return ClauseType.unknown; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/query-clauses.ts: -------------------------------------------------------------------------------- 1 | import { IFilterExpr, IQueryController } from ".."; 2 | import { IBaseController } from "../controllers/base-controller"; 3 | import { ClauseType } from "../enum"; 4 | import { StrategyHandler } from "../strategy-handler"; 5 | import { IQueryClause } from "./query-clause"; 6 | 7 | export interface IQueryClauses { 8 | addClause(clause: IQueryClause): IQueryClauses; 9 | addExpressions(...expressions: IFilterExpr[]): IQueryClauses; 10 | addAsExpression(key: string, config: any): IQueryClauses; 11 | current: IQueryClause; 12 | count: number; 13 | map: any; 14 | } 15 | 16 | export class QueryClauses extends StrategyHandler { 17 | list: IQueryClause[] = []; 18 | controller?: IBaseController; 19 | 20 | get map() { 21 | return this.controller?.map; 22 | } 23 | 24 | setController(controller: IBaseController) { 25 | this.controller = controller; 26 | return this; 27 | } 28 | 29 | get count() { 30 | return this.list.length; 31 | } 32 | 33 | get current(): IQueryClause { 34 | return this.list[this.list.length - 1]; 35 | } 36 | 37 | addAsExpression(key: string, config: any): IQueryClauses { 38 | this.current.addAsExpression(key, config); 39 | return this; 40 | } 41 | 42 | addClause(clause: IQueryClause) { 43 | clause.setContainer(this); 44 | if (!this.isValid(clause)) { 45 | this.error(`addClause: Invalid ${this.typeName} clause`, clause); 46 | return this; 47 | } 48 | this.list.push(clause); 49 | return this; 50 | } 51 | 52 | addExpressions(...expressions: IFilterExpr[]) { 53 | this.current.addExpressions(...expressions); 54 | return this; 55 | } 56 | 57 | isValid(clause: IQueryClause) { 58 | return false; 59 | } 60 | 61 | get typeName(): string { 62 | return ClauseType[this.type]; 63 | } 64 | 65 | get type() { 66 | this.error("Must be implemented by subclass"); 67 | return ClauseType.unknown; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/result-clause.ts: -------------------------------------------------------------------------------- 1 | import { QueryClause } from "."; 2 | import { ClauseType } from "../enum"; 3 | import { IQueryClause } from "./query-clause"; 4 | 5 | export interface IResultClause extends IQueryClause {} 6 | 7 | export class ResultClause extends QueryClause { 8 | get type(): ClauseType { 9 | return ClauseType.where; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/result-clauses.ts: -------------------------------------------------------------------------------- 1 | import { IQueryClauses } from "."; 2 | import { ResultController } from ".."; 3 | import { ClauseType } from "../enum"; 4 | import { QueryClauses } from "./query-clauses"; 5 | import { ResultClause } from "./return-clause"; 6 | 7 | export interface IResultClauses extends IQueryClauses { 8 | addClause(clause: ResultClause): IResultClauses; 9 | } 10 | 11 | export class ResultClauses extends QueryClauses implements IResultClauses { 12 | controller?: ResultController; 13 | 14 | get map() { 15 | return this.controller?.map; 16 | } 17 | 18 | addClause(clause: ResultClause) { 19 | return super.addClause(clause); 20 | } 21 | 22 | isValid(clause: ResultClause) { 23 | return clause.type === ClauseType.return; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/return-clause.ts: -------------------------------------------------------------------------------- 1 | import { QueryClause } from "."; 2 | import { ClauseType } from "../enum"; 3 | import { IQueryClause } from "./query-clause"; 4 | 5 | export interface IReturnClause extends IQueryClause {} 6 | 7 | export class ReturnClause extends QueryClause { 8 | get type(): ClauseType { 9 | return ClauseType.where; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/return-clauses.ts: -------------------------------------------------------------------------------- 1 | import { IQueryClauses } from "."; 2 | import { ReturnController } from ".."; 3 | import { ClauseType } from "../enum"; 4 | import { QueryClauses } from "./query-clauses"; 5 | import { ReturnClause } from "./return-clause"; 6 | 7 | export interface IReturnClauses extends IQueryClauses { 8 | addClause(clause: ReturnClause): IReturnClauses; 9 | } 10 | 11 | export class ReturnClauses extends QueryClauses implements IReturnClauses { 12 | controller?: ReturnController; 13 | 14 | get map() { 15 | return this.controller?.map; 16 | } 17 | 18 | addClause(clause: ReturnClause) { 19 | return super.addClause(clause); 20 | } 21 | 22 | isValid(clause: ReturnClause) { 23 | return clause.type === ClauseType.return; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/where-clause.ts: -------------------------------------------------------------------------------- 1 | import { ClauseType, WhereFilterType } from "../enum"; 2 | import { IQueryClause, QueryClause } from "./query-clause"; 3 | 4 | export interface IWhereClause extends IQueryClause {} 5 | 6 | export class WhereClause extends QueryClause implements IQueryClause { 7 | subtype: WhereFilterType = WhereFilterType.must; 8 | 9 | get type(): ClauseType { 10 | return ClauseType.where; 11 | } 12 | 13 | createExpression(key: string, config: any) { 14 | return this.strategyMap.where.boolean.and(config); 15 | } 16 | 17 | isOptional() { 18 | return this.subtype === WhereFilterType.optional; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/cypher/strategy/clauses/where-clauses.ts: -------------------------------------------------------------------------------- 1 | import { IWhereClause } from "."; 2 | import { WhereController } from ".."; 3 | import { ClauseType } from "../enum"; 4 | import { IQueryClauses, QueryClauses } from "./query-clauses"; 5 | import { WhereClause } from "./where-clause"; 6 | 7 | export interface IWhereClauses extends IQueryClauses { 8 | addClause(clause: IWhereClause): IWhereClauses; 9 | } 10 | 11 | export class WhereClauses extends QueryClauses implements IWhereClauses { 12 | controller?: WhereController; 13 | 14 | get map() { 15 | return this.controller?.map; 16 | } 17 | 18 | get exprMapKeys(): string[] { 19 | return ["boolean", "labels", "props"]; 20 | } 21 | 22 | addClause(clause: IWhereClause) { 23 | return super.addClause(clause); 24 | } 25 | 26 | isValid(clause: IWhereClause) { 27 | return clause.type === ClauseType.where; 28 | } 29 | 30 | get optional(): IWhereClause[] { 31 | return this.list.filter((item) => (item as WhereClause).isOptional()); 32 | } 33 | 34 | get must(): IWhereClause[] { 35 | return this.list.filter((item) => !(item as WhereClause).isOptional()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/cypher/strategy/controllers/base-controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IAliasFilterExpr, 3 | IFilterExpr, 4 | IQueryClause, 5 | IQueryClauses, 6 | QueryClauses, 7 | } from ".."; 8 | import { StrategyHandler } from "../strategy-handler"; 9 | 10 | export interface IBaseController { 11 | clauses: IQueryClauses; 12 | currentClause: IQueryClause; 13 | latestExpr: IFilterExpr; 14 | setAliasFilterExpr(aliasFilterExpr: IAliasFilterExpr): IBaseController; 15 | addExpressions(...expressions: IFilterExpr[]): IBaseController; 16 | addAsExpression(key: string, config: any): IBaseController; 17 | map: any; 18 | } 19 | 20 | export class BaseController extends StrategyHandler implements IBaseController { 21 | clauses: IQueryClauses = new QueryClauses(this.strategy); 22 | 23 | get currentClause(): IQueryClause { 24 | return this.clauses.current; 25 | } 26 | 27 | get latestExpr(): IFilterExpr { 28 | return this.currentClause.latestExpr; 29 | } 30 | 31 | get map() { 32 | return {}; 33 | } 34 | 35 | addClause(clause: IQueryClause) { 36 | this.clauses.addClause(clause); 37 | } 38 | 39 | addAsExpression(key: string, config: any): IBaseController { 40 | this.currentClause.addAsExpression(key, config); 41 | return this; 42 | } 43 | 44 | addExpressions(...expressions: IFilterExpr[]) { 45 | this.clauses.addExpressions(...expressions); 46 | return this; 47 | } 48 | 49 | setAliasFilterExpr(filter: IAliasFilterExpr) { 50 | this.clauses.current.setAliasFilterExpr(filter); 51 | return this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/cypher/strategy/controllers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./query-controller"; 2 | export * from "./match-controller"; 3 | export * from "./where-controller"; 4 | export * from "./return-controller"; 5 | export * from "./result-controller"; 6 | -------------------------------------------------------------------------------- /src/cypher/strategy/controllers/match-controller.ts: -------------------------------------------------------------------------------- 1 | import { IMatchClauses, MatchClauses } from "../clauses"; 2 | import { BaseController } from "./base-controller"; 3 | 4 | export interface IMatchController { 5 | clauses: IMatchClauses; 6 | } 7 | 8 | export class MatchController 9 | extends BaseController 10 | implements IMatchController 11 | { 12 | clauses: IMatchClauses = new MatchClauses(this.strategy).setController(this); 13 | 14 | get map() { 15 | return this.strategyMap.match; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/cypher/strategy/controllers/query-controller.ts: -------------------------------------------------------------------------------- 1 | import { IAliasFilterExpr, IFilterExpr, IMatchFilter } from ".."; 2 | import { GraphObjDef, IQueryResult } from "../../cypher-types"; 3 | import { 4 | MatchController, 5 | ResultController, 6 | ReturnController, 7 | WhereController, 8 | } from "."; 9 | import { StrategyHandler } from "../strategy-handler"; 10 | import { IBaseController } from "./base-controller"; 11 | 12 | export interface IControllersMap { 13 | [key: string]: IBaseController; 14 | } 15 | 16 | export interface IQueryController { 17 | controllers: IControllersMap; 18 | latestExpr?: IFilterExpr; 19 | setAliasFilterExpr(aliasFilterExpr: IAliasFilterExpr): IQueryController; 20 | run(objs: GraphObjDef[]): IQueryResult | undefined; 21 | } 22 | 23 | export class QueryController 24 | extends StrategyHandler 25 | implements IQueryController 26 | { 27 | controllers: IControllersMap = { 28 | match: new MatchController(this.strategy), 29 | where: new WhereController(this.strategy), 30 | return: new ReturnController(this.strategy), 31 | result: new ResultController(this.strategy), 32 | }; 33 | 34 | latestController?: IBaseController; 35 | 36 | get latestExpr() { 37 | return this.latestController && this.latestController.latestExpr; 38 | } 39 | 40 | run(objs: GraphObjDef[]): IQueryResult | undefined { 41 | return; 42 | } 43 | 44 | addAsExpression(name: string, key: string, config: any): IQueryController { 45 | const controller = this.controllers[name]; 46 | this.latestController = controller; 47 | controller.addAsExpression(key, config); 48 | controller.currentClause.latestExpr; 49 | return this; 50 | } 51 | 52 | addFilter(filter: IFilterExpr) { 53 | // this.filters.push(filter); 54 | return this; 55 | } 56 | 57 | setAliasFilterExpr(aliasFilterExpr: IAliasFilterExpr) { 58 | return this; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cypher/strategy/controllers/result-controller.ts: -------------------------------------------------------------------------------- 1 | import { IResultClauses, ResultClauses } from "../clauses"; 2 | import { BaseController } from "./base-controller"; 3 | 4 | export interface IResultController { 5 | clauses: IResultClauses; 6 | } 7 | 8 | export class ResultController 9 | extends BaseController 10 | implements IResultController 11 | { 12 | clauses: IResultClauses = new ResultClauses(this.strategy).setController( 13 | this 14 | ); 15 | 16 | get map() { 17 | return this.strategyMap.return; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/cypher/strategy/controllers/return-controller.ts: -------------------------------------------------------------------------------- 1 | import { IReturnClauses, ReturnClauses } from "../clauses"; 2 | import { BaseController } from "./base-controller"; 3 | 4 | export interface IReturnController { 5 | clauses: IReturnClauses; 6 | } 7 | 8 | export class ReturnController 9 | extends BaseController 10 | implements IReturnController 11 | { 12 | clauses: IReturnClauses = new ReturnClauses(this.strategy).setController( 13 | this 14 | ); 15 | 16 | get map() { 17 | return this.strategyMap.return; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/cypher/strategy/controllers/where-controller.ts: -------------------------------------------------------------------------------- 1 | import { IWhereClauses, WhereClauses } from "../clauses"; 2 | import { BaseController, IBaseController } from "./base-controller"; 3 | 4 | export interface IWhereController extends IBaseController { 5 | clauses: IWhereClauses; 6 | } 7 | 8 | export class WhereController 9 | extends BaseController 10 | implements IWhereController 11 | { 12 | clauses: IWhereClauses = new WhereClauses(this.strategy).setController(this); 13 | 14 | get map() { 15 | return this.strategyMap.where; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/cypher/strategy/defaults.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createAndFilterExpr, 3 | createLimitExpr, 4 | createNodeLabelMatchesExpr, 5 | createNodeLabelsIncludeExpr, 6 | createNodePropEqlExpr, 7 | createNodePropGtExpr, 8 | createNodePropLtExpr, 9 | createNotFilterExpr, 10 | createOrFilterExpr, 11 | createSkipExpr, 12 | createStrategyFilter, 13 | createStrategyResult, 14 | createUnionExpr, 15 | } from "."; 16 | import { IStrategyMap } from "./map"; 17 | 18 | export const defaultPropsFilterMap = () => { 19 | return { 20 | eq: createNodePropEqlExpr, 21 | lt: createNodePropLtExpr, 22 | gt: createNodePropGtExpr, 23 | }; 24 | }; 25 | 26 | export const defaultLabelsFilterMap = () => { 27 | return { 28 | match: createNodeLabelMatchesExpr, 29 | include: createNodeLabelsIncludeExpr, 30 | }; 31 | }; 32 | 33 | export const defaultBooleanFilterMap = () => { 34 | return { 35 | and: createAndFilterExpr, 36 | or: createOrFilterExpr, 37 | not: createNotFilterExpr, 38 | }; 39 | }; 40 | 41 | export const defaultFilterMap = () => { 42 | return { 43 | root: createStrategyFilter, 44 | exprMap: { 45 | boolean: defaultBooleanFilterMap(), 46 | props: defaultPropsFilterMap(), 47 | labels: defaultLabelsFilterMap(), 48 | }, 49 | }; 50 | }; 51 | 52 | export const defaultResultMap = () => { 53 | return { 54 | root: createStrategyResult, 55 | exprMap: { 56 | limit: createLimitExpr, 57 | skip: createSkipExpr, 58 | union: createUnionExpr, 59 | }, 60 | }; 61 | }; 62 | 63 | const empty: any = {}; 64 | 65 | export const defaultStrategyMap = (): IStrategyMap => { 66 | return { 67 | create: { 68 | root: empty, 69 | }, 70 | delete: { 71 | root: empty, 72 | }, 73 | match: { 74 | root: empty, 75 | }, 76 | filter: defaultFilterMap(), 77 | result: defaultResultMap(), 78 | }; 79 | }; 80 | -------------------------------------------------------------------------------- /src/cypher/strategy/enum.ts: -------------------------------------------------------------------------------- 1 | export enum ClauseType { 2 | unknown = "unknown", 3 | match = "match", 4 | where = "where", 5 | return = "return", 6 | } 7 | 8 | export enum WhereFilterType { 9 | none = "none", 10 | optional = "optional", 11 | must = "must", 12 | } 13 | -------------------------------------------------------------------------------- /src/cypher/strategy/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./read"; 2 | export * from "./strategy"; 3 | export * from "./clauses"; 4 | export * from "./controllers"; 5 | export * from "./util"; 6 | export * from "./enum"; 7 | -------------------------------------------------------------------------------- /src/cypher/strategy/map.ts: -------------------------------------------------------------------------------- 1 | import { IReturnExpr, IStrategyResult } from "../cypher-types"; 2 | import { IFilterExpr, IMatchObjExpr } from ".."; 3 | import { 4 | IComposeOneFilterExpr, 5 | ICompositeFilterExpr, 6 | ICypherStrategy, 7 | } from "."; 8 | 9 | type AndOrFilterExprFactoryFn = CompositeFilterExprFactoryFn; 10 | type NotFilterExprFactoryFn = ComposeOneFilterExprFactoryFn; 11 | 12 | type CompositeFilterExprFactoryFn = (config?: any) => ICompositeFilterExpr; 13 | type ComposeOneFilterExprFactoryFn = (config?: any) => IComposeOneFilterExpr; 14 | 15 | type FilterExprFactoryFn = (config?: any) => IFilterExpr; 16 | type ReturnExprFactoryFn = ( 17 | result: IStrategyResult, 18 | config?: any 19 | ) => IReturnExpr; 20 | 21 | export interface IPropFilterExprMap { 22 | eq: FilterExprFactoryFn; 23 | gt: FilterExprFactoryFn; 24 | lt: FilterExprFactoryFn; 25 | } 26 | 27 | export interface ILabelsFilterExprMap { 28 | include: FilterExprFactoryFn; 29 | match: FilterExprFactoryFn; 30 | } 31 | 32 | export interface IBooleanFilterExprMap { 33 | and: AndOrFilterExprFactoryFn; 34 | or: AndOrFilterExprFactoryFn; 35 | not: NotFilterExprFactoryFn; 36 | } 37 | 38 | export interface IWhereExprMap { 39 | boolean: IBooleanFilterExprMap; 40 | props: IPropFilterExprMap; 41 | labels: ILabelsFilterExprMap; 42 | } 43 | 44 | export interface IReturnExprMap { 45 | limit: ReturnExprFactoryFn; 46 | skip: ReturnExprFactoryFn; 47 | union: ReturnExprFactoryFn; 48 | } 49 | 50 | export type StrategyRootFactoryFn = (config: any) => ICypherStrategy; 51 | 52 | export type ResultRootFactoryFn = (config: any) => IStrategyResult; 53 | 54 | type MatchRootExprFactoryFn = (config: any) => IMatchObjExpr; 55 | 56 | interface IDeleteExpr {} 57 | interface ICreateExpr {} 58 | 59 | type DeleteRootExprFactoryFn = (config: any) => IDeleteExpr; 60 | 61 | type CreateRootExprFactoryFn = (config: any) => ICreateExpr; 62 | 63 | export interface IStrategyMap { 64 | create: { 65 | root: CreateRootExprFactoryFn; 66 | }; 67 | delete: { 68 | root: DeleteRootExprFactoryFn; 69 | }; 70 | match: { 71 | root: MatchRootExprFactoryFn; 72 | }; 73 | where: IWhereExprMap; 74 | return: IReturnExprMap; 75 | // root: ResultRootFactoryFn; 76 | } 77 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./match"; 2 | export * from "./result"; 3 | export * from "./return"; 4 | export * from "./where"; 5 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/match/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./match-obj-expr"; 2 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/match/match-obj-expr.ts: -------------------------------------------------------------------------------- 1 | import { GraphObjDef, Props } from "../../../cypher-types"; 2 | 3 | export interface ObjMatchConfigObj { 4 | alias?: string; 5 | labels?: string[]; 6 | props?: Props; 7 | } 8 | 9 | export interface IMatchFilter extends IMatchObjExpr {} 10 | 11 | export interface IMatchObjExpr { 12 | config(config: ObjMatchConfigObj): IMatchObjExpr; 13 | } 14 | 15 | export const createMatchObjExpr = (config: ObjMatchConfigObj) => 16 | new MatchObjExpr().config(config); 17 | 18 | export class MatchObjExpr { 19 | alias: string = "_"; 20 | labels: string[] = []; 21 | props: Props = {}; 22 | 23 | config(config: ObjMatchConfigObj) { 24 | const { alias, labels, props } = config; 25 | this.setAlias(alias); 26 | this.setLabels(labels); 27 | this.setProps(props); 28 | return this; 29 | } 30 | 31 | setAlias(alias: string = "_") { 32 | this.alias = alias; 33 | return this; 34 | } 35 | 36 | setLabels(labels: string[] = []) { 37 | this.labels = labels; 38 | return this; 39 | } 40 | 41 | setProps(props: Props = {}) { 42 | this.props = props; 43 | return this; 44 | } 45 | 46 | run(obj: any): GraphObjDef | undefined { 47 | return obj; 48 | } 49 | 50 | runAll(objs: GraphObjDef[]): GraphObjDef[] { 51 | return objs.reduce((acc: GraphObjDef[], obj) => { 52 | const result = this.run(obj); 53 | if (result) { 54 | acc.push(result); 55 | } 56 | return acc; 57 | }, []); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/result/group-expr.ts: -------------------------------------------------------------------------------- 1 | import { ReturnExpr } from "../return"; 2 | 3 | type itemAttrFn = (value: any) => any; 4 | 5 | type itemAttrGrouper = itemAttrFn | string; 6 | 7 | const groupBy = (list: any[], fn: itemAttrGrouper) => 8 | list.reduce((acc, value) => { 9 | const rfn: itemAttrFn = 10 | typeof fn === "function" ? fn : (val: any) => val[fn]; 11 | // Group initialization 12 | if (!acc[rfn(value)]) { 13 | acc[rfn(value)] = []; 14 | } 15 | 16 | // Grouping 17 | acc[rfn(value)].push(value); 18 | 19 | return acc; 20 | }, {}); 21 | 22 | export const createGroupExpr = (config?: any) => new GroupExpr().config(config); 23 | 24 | export class GroupExpr extends ReturnExpr { 25 | key: itemAttrGrouper = "label"; 26 | 27 | run() { 28 | if (!this.hasValidResults(this.queryResult)) { 29 | return this.queryResult; 30 | } 31 | this.queryResult.data = groupBy(this.queryResult.data, this.key); 32 | return this.queryResult; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/result/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./group-expr"; 2 | export * from "./limit-expr"; 3 | export * from "./order-expr"; 4 | export * from "./result"; 5 | export * from "./skip-expr"; 6 | export * from "./union-expr"; 7 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/result/limit-expr.ts: -------------------------------------------------------------------------------- 1 | import { ReturnExpr } from "../return/return-expr"; 2 | 3 | export const createLimitExpr = (config?: any) => new LimitExpr().config(config); 4 | 5 | export class LimitExpr extends ReturnExpr { 6 | run() { 7 | if (!this.hasValidResults(this.queryResult)) { 8 | return this.queryResult; 9 | } 10 | this.num && this.queryResult.data.splice(0, this.num); 11 | return this.queryResult; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/result/order-expr.ts: -------------------------------------------------------------------------------- 1 | export class OrderExpr implements IStrategy { 2 | run() {} 3 | } 4 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/result/result.ts: -------------------------------------------------------------------------------- 1 | import { emptyResults, IReturnExpr, ReturnExpr } from "../return"; 2 | import { IQueryResult } from "../../../cypher-types"; 3 | import { IFilterResult } from "../where"; 4 | import { 5 | FilterResultConverter, 6 | IFilterResultConverter, 7 | } from "../where/filter-result-converter"; 8 | 9 | export const createStrategyResult = ( 10 | config: IStrategyResultConfig = {} 11 | ): IStrategyResult => new StrategyResult(config); 12 | 13 | export interface IStrategyResultConfig { 14 | converter?: IFilterResultConverter; 15 | } 16 | 17 | export interface IStrategyResult { 18 | setFiltered(filtered: IFilterResult): IStrategyResult; 19 | addExpr(expr: IReturnExpr): IStrategyResult; 20 | } 21 | 22 | export class StrategyResult { 23 | filtered: IFilterResult = {}; 24 | results: IQueryResult = emptyResults(); 25 | expressions: IReturnExpr[] = []; 26 | converter: IFilterResultConverter; 27 | 28 | constructor(config: IStrategyResultConfig = {}) { 29 | this.converter = config.converter || this.createFilterResultConverter(); 30 | } 31 | 32 | createFilterResultConverter() { 33 | return new FilterResultConverter(); 34 | } 35 | 36 | setFiltered(filtered: IFilterResult) { 37 | this.filtered = filtered; 38 | this.results = this.converter.toQueryResult(this.filtered); 39 | return this; 40 | } 41 | 42 | addExpr(expr: IReturnExpr) { 43 | expr.setResults(this.results); 44 | this.expressions.push(expr); 45 | return this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/result/skip-expr.ts: -------------------------------------------------------------------------------- 1 | import { ReturnExpr } from "../return/return-expr"; 2 | 3 | export const createSkipExpr = (config: any) => new SkipExpr().config(config); 4 | 5 | export class SkipExpr extends ReturnExpr { 6 | run() { 7 | if (!this.hasValidResults(this.queryResult)) { 8 | return this.queryResult; 9 | } 10 | this.num && this.queryResult.data.splice(this.num); 11 | return this.queryResult; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/result/union-expr.ts: -------------------------------------------------------------------------------- 1 | import { IQueryResult } from "../../../cypher-types"; 2 | import { emptyResults, ReturnExpr } from "../return/return-expr"; 3 | 4 | export const createUnionExpr = (config: any) => new UnionExpr().config(config); 5 | 6 | export class UnionExpr extends ReturnExpr { 7 | results2: IQueryResult = emptyResults(); 8 | 9 | config(config: any) { 10 | super.config(config); 11 | this.setUnionResults(config.results); 12 | return this; 13 | } 14 | 15 | setUnionResults(results: IQueryResult) { 16 | this.results2 = results; 17 | return this; 18 | } 19 | 20 | hasSameHeaders(results: IQueryResult, results2: IQueryResult) { 21 | return results.headers.every((header) => results2.headers.includes(header)); 22 | } 23 | 24 | run() { 25 | if (!this.hasValidResults(this.queryResult)) { 26 | this.error("Source results to unite is invalid"); 27 | } 28 | if (!this.hasValidResults(this.results2)) { 29 | this.error("Target results to unite is invalid"); 30 | } 31 | if (!this.hasSameHeaders(this.queryResult, this.results2)) { 32 | this.error("Results to unite must have same headers"); 33 | } 34 | this.queryResult.data.push(...this.results2.data); 35 | return this.queryResult; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/return/aggregation-expr.ts: -------------------------------------------------------------------------------- 1 | import { ReturnExpr } from "."; 2 | import { GraphObjDef } from "../../../cypher-types"; 3 | 4 | export const createAggregationExpr = (config?: any) => 5 | new ReturnAggregationExpr().config(config); 6 | 7 | export class ReturnAggregationExpr extends ReturnExpr { 8 | functionName: string = "sum"; 9 | key: string = ""; 10 | propName: string = ""; 11 | 12 | name: string = "aggregation"; 13 | 14 | functionMap: any = { 15 | sum: (acc: number, value: number) => (acc += value), 16 | }; 17 | 18 | functionAliasMap: any = { 19 | avg: "sum", 20 | }; 21 | 22 | transformMap: any = { 23 | avg: (result: any, data: any[]) => result / data.length, 24 | }; 25 | 26 | get data() { 27 | return this.filterResult[this.key]; 28 | } 29 | 30 | get fnName() { 31 | return this.functionAliasMap[this.functionName] || this.functionName; 32 | } 33 | 34 | get aggregateFn() { 35 | return this.functionMap[this.fnName]; 36 | } 37 | 38 | get transformFn() { 39 | return this.transformMap[this.fnName]; 40 | } 41 | 42 | run() { 43 | const { queryResult, functionName, data, aggregateFn, transformFn } = this; 44 | if (!this.hasValidResults(queryResult)) { 45 | return queryResult; 46 | } 47 | if (!aggregateFn) { 48 | this.error(`Invalid aggregation function ${functionName}`); 49 | } 50 | const result = data.reduce(aggregateFn, 0); 51 | return transformFn ? transformFn(result, data) : result; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/return/count-expr.ts: -------------------------------------------------------------------------------- 1 | import { ReturnExpr } from "."; 2 | 3 | export const createCountExpr = (config?: any) => 4 | new ReturnCountExpr().config(config); 5 | 6 | export class ReturnCountExpr extends ReturnExpr { 7 | name: string = "count"; 8 | 9 | key: string = "*"; 10 | 11 | run() { 12 | if (!this.hasValidResults(this.queryResult)) { 13 | return this.queryResult; 14 | } 15 | const rows = this.filterResult[this.key]; 16 | return rows.length; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/return/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./count-expr"; 2 | export * from "./label-expr"; 3 | export * from "./prop-expr"; 4 | export * from "./aggregation-expr"; 5 | export * from "./return-expr"; 6 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/return/label-expr.ts: -------------------------------------------------------------------------------- 1 | import { ReturnExpr } from "."; 2 | import { GraphObjDef } from "../../../cypher-types"; 3 | import { ReturnObjValueExpr } from "./return-obj-value-expr"; 4 | 5 | export class ReturnLabelExpr extends ReturnObjValueExpr { 6 | name: string = "label"; 7 | 8 | label: string = ""; 9 | 10 | mapObj(obj: GraphObjDef): any { 11 | const labels = obj.labels || []; 12 | return labels.find((label) => label === this.label); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/return/prop-expr.ts: -------------------------------------------------------------------------------- 1 | import { GraphObjDef } from "../../../cypher-types"; 2 | import { ReturnObjValueExpr } from "./return-obj-value-expr"; 3 | 4 | export class ReturnPropExpr extends ReturnObjValueExpr { 5 | name: string = "prop"; 6 | 7 | propName: string = ""; 8 | 9 | mapObj(obj: GraphObjDef): any { 10 | const props = obj.props || {}; 11 | return props[this.propName]; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/return/return-expr.ts: -------------------------------------------------------------------------------- 1 | import { IStrategyResult } from ".."; 2 | import { IFilterResult } from "../.."; 3 | import { Handler } from "../../../builder/handler"; 4 | import { IQueryResult } from "../../../cypher-types"; 5 | 6 | const createData = (num: number): any[] => 7 | [...Array(num)].map((e, i) => ({ id: i })); 8 | 9 | const createHeaders = (num: number): string[] => 10 | [...Array(num)].map((e, i) => String.fromCharCode(i + 65)); 11 | 12 | export const createResults = (num: number): IQueryResult => { 13 | const results = emptyResults(); 14 | results.headers = createHeaders(num); 15 | results.data = createData(num); 16 | return results; 17 | }; 18 | 19 | export const emptyResults = (): IQueryResult => ({ 20 | headers: [], 21 | data: [], 22 | get columns() { 23 | return this.headers.length; 24 | }, 25 | get rows() { 26 | return this.data.length; 27 | }, 28 | }); 29 | 30 | type NodeResultConfigObj = { 31 | num?: number; 32 | alias: string; 33 | }; 34 | 35 | export interface IReturnExpr { 36 | filterResult: IFilterResult; 37 | queryResult: IQueryResult; 38 | result?: IStrategyResult; 39 | setResult(result: IStrategyResult): IReturnExpr; 40 | setResults(queryResult: IQueryResult): IReturnExpr; 41 | run(): any; 42 | } 43 | 44 | export class ReturnExpr extends Handler implements IReturnExpr { 45 | filterResult: IFilterResult = {}; 46 | queryResult: IQueryResult = emptyResults(); 47 | result?: IStrategyResult; 48 | num?: number; 49 | alias: string = "_"; 50 | 51 | constructor() { 52 | super(); 53 | } 54 | 55 | setResult(result: IStrategyResult) { 56 | this.result = result; 57 | return this; 58 | } 59 | 60 | config(configObj: NodeResultConfigObj) { 61 | this.setNumber(configObj.num); 62 | this.setAlias(configObj.alias); 63 | return this; 64 | } 65 | 66 | setAlias(alias: string) { 67 | this.alias = alias; 68 | return this; 69 | } 70 | 71 | setNumber(num?: number) { 72 | this.num = num; 73 | return this; 74 | } 75 | 76 | setResults(queryResult: IQueryResult) { 77 | this.queryResult = queryResult; 78 | return this; 79 | } 80 | 81 | hasValidResults(results: IQueryResult) { 82 | return !(results && results.data.length); 83 | } 84 | 85 | run() { 86 | throw new Error("ResultExpr sublcass must implement run() method"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/return/return-obj-value-expr.ts: -------------------------------------------------------------------------------- 1 | import { ReturnExpr } from "."; 2 | import { GraphObjDef } from "../../../cypher-types"; 3 | 4 | export interface IReturnValue {} 5 | 6 | export abstract class ReturnObjValueExpr extends ReturnExpr { 7 | alias: string = ""; 8 | 9 | mapObj(obj: GraphObjDef): any { 10 | throw "Must be implemented by subclass"; 11 | } 12 | 13 | runAll(objs: GraphObjDef[]): IReturnValue[] { 14 | const mapFn = this.mapObj.bind(this); 15 | return objs.map(mapFn); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/return/return-value.ts: -------------------------------------------------------------------------------- 1 | export interface IReturnValue {} 2 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/alias-filter.ts: -------------------------------------------------------------------------------- 1 | import { FilterExpr, IFilterExpr } from "."; 2 | import { ICypherStrategy } from "../.."; 3 | import { IGraphObjApi } from "../../../.."; 4 | import { GraphObjDef } from "../../../cypher-types"; 5 | 6 | export interface IAliasFilterExpr extends IFilterExpr { 7 | strategy?: ICypherStrategy; 8 | alias: string; 9 | matchedResults: GraphObjDef[]; 10 | setMatchedResults(matchedResults: GraphObjDef[]): IAliasFilterExpr; 11 | } 12 | 13 | export class AliasFilterExpr extends FilterExpr implements IAliasFilterExpr { 14 | alias: string = "_"; 15 | matchedResults: GraphObjDef[] = []; 16 | 17 | setMatchedResults(matchedResults: GraphObjDef[]) { 18 | this.matchedResults = matchedResults; 19 | return this; 20 | } 21 | 22 | get isOptional() { 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/boolean/and-expr.ts: -------------------------------------------------------------------------------- 1 | import { CompositeFilterResult, IFilterExpr } from ".."; 2 | import { GraphObjDef } from "../../../../cypher-types"; 3 | import { 4 | CompositeFilterExpr, 5 | ICompositeFilterExpr, 6 | } from "../composite-filter-expr"; 7 | 8 | export const createAndFilterExpr = (config?: any) => 9 | new AndFilterExpr().config(config); 10 | 11 | export class AndCompositeFilterResult extends CompositeFilterResult { 12 | allValid(): boolean { 13 | return this.results.every((result) => result.length > 0); 14 | } 15 | 16 | composedResult(): GraphObjDef[] { 17 | return this.allValid() ? super.composedResult() : []; 18 | } 19 | } 20 | 21 | export interface IAndFilterExpr extends ICompositeFilterExpr {} 22 | 23 | export class AndFilterExpr extends CompositeFilterExpr { 24 | name: string = "and"; 25 | 26 | createCompositeResult() { 27 | return new AndCompositeFilterResult(); 28 | } 29 | 30 | createReduceComposed(objs: GraphObjDef[]): any { 31 | return (acc: AndCompositeFilterResult, filter: IFilterExpr) => { 32 | let results = filter.runAll(acc.combinedResults); 33 | results = acc.setOps.intersection(acc.combinedResults, results); 34 | acc.combinedResults = results; 35 | return acc; 36 | }; 37 | } 38 | 39 | run(obj: any): GraphObjDef | undefined { 40 | return this.runAll([obj])[0]; 41 | } 42 | 43 | runAll(objs: any): GraphObjDef[] { 44 | const { composedFilters } = this; 45 | if (!composedFilters || composedFilters.length === 0) { 46 | this.error("Missing composed filters"); 47 | return []; 48 | } 49 | return this.runComposed(objs); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/boolean/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./and-expr"; 2 | export * from "./not.expr"; 3 | export * from "./or-expr"; 4 | export * from "./set-operations"; 5 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/boolean/not.expr.ts: -------------------------------------------------------------------------------- 1 | import { CompositeFilterResult, IComposeOneFilterExpr } from ".."; 2 | import { GraphObjDef } from "../../../../cypher-types"; 3 | import { ComposeOneFilterExpr } from "../compose-one-filter-expr"; 4 | 5 | export const createNotFilterExpr = (config?: any) => 6 | new NotFilterExpr().config(config); 7 | 8 | export class NotCompositeFilterResult extends CompositeFilterResult { 9 | composedResult(): GraphObjDef[] { 10 | return this.combinedResults; 11 | } 12 | } 13 | 14 | export interface INotFilterExpr extends IComposeOneFilterExpr {} 15 | 16 | export class NotFilterExpr extends ComposeOneFilterExpr { 17 | name: string = "not"; 18 | 19 | createCompositeResult() { 20 | return new NotCompositeFilterResult(); 21 | } 22 | 23 | inverse(results: any[]): GraphObjDef[] { 24 | return this.setOps.difference(results, this.results); 25 | } 26 | 27 | run(obj: any): GraphObjDef | undefined { 28 | return this.runAll([obj])[0]; 29 | } 30 | 31 | runAll(objs: any): GraphObjDef[] { 32 | const { composedFilters } = this; 33 | if (!composedFilters || composedFilters.length === 0) { 34 | this.error("Missing composed filters"); 35 | return []; 36 | } 37 | const results = this.runComposed(objs); 38 | return this.inverse(results); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/boolean/or-expr.ts: -------------------------------------------------------------------------------- 1 | import { CompositeFilterResult, ICompositeFilterExpr, IFilterExpr } from ".."; 2 | import { GraphObjDef } from "../../../../cypher-types"; 3 | import { IAliasFilterExpr } from "../alias-filter"; 4 | import { CompositeFilterExpr } from "../composite-filter-expr"; 5 | 6 | export const createOrFilterExpr = (config?: any) => 7 | new OrFilterExpr().config(config); 8 | 9 | export class OrCompositeFilterResult extends CompositeFilterResult { 10 | allValid(): boolean { 11 | return this.results.every((result) => result.length > 0); 12 | } 13 | } 14 | 15 | export interface IOrFilterExpr extends ICompositeFilterExpr {} 16 | 17 | export class OrFilterExpr extends CompositeFilterExpr { 18 | name: string = "or"; 19 | 20 | createReduceComposed(objs: GraphObjDef[]): any { 21 | return (acc: OrCompositeFilterResult, filter: IFilterExpr) => { 22 | let results = filter.runAll(objs); 23 | results = acc.setOps.union(acc.combinedResults, results); 24 | acc.combinedResults = results; 25 | return acc; 26 | }; 27 | } 28 | 29 | createCompositeResult() { 30 | return new OrCompositeFilterResult(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/boolean/set-operations.ts: -------------------------------------------------------------------------------- 1 | export interface ISetOperations { 2 | intersection(arrA: any[], arrB: any[]): any[]; 3 | difference(arrA: any[], arrB: any[]): any[]; 4 | union(arrA: any[], arrB: any[]): any[]; 5 | } 6 | 7 | export const setOperations = { 8 | intersection: (arrA: any[], arrB: any[]): any[] => { 9 | return arrA.filter((x) => arrB.includes(x)); 10 | }, 11 | 12 | difference: (arrA: any[], arrB: any[]): any[] => { 13 | return arrA.filter((x) => !arrB.includes(x)); 14 | }, 15 | 16 | union: (arrA: any[], arrB: any[]): any[] => { 17 | return [...new Set([...arrA, ...arrB])]; 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/compose-one-filter-expr.ts: -------------------------------------------------------------------------------- 1 | import { CompositeFilterExpr, IFilterExpr } from "../.."; 2 | 3 | export interface IComposeOneFilterExpr { 4 | setComposedFilter(filterExpr: IFilterExpr): IComposeOneFilterExpr; 5 | } 6 | 7 | export class ComposeOneFilterExpr 8 | extends CompositeFilterExpr 9 | implements IFilterExpr 10 | { 11 | composedFilter?: IFilterExpr; 12 | 13 | setComposedFilter(filterExpr: IFilterExpr) { 14 | this.composedFilter = filterExpr; 15 | return this; 16 | } 17 | 18 | get filtersToReduce(): IFilterExpr[] { 19 | return this.composedFilter ? [this.composedFilter] : []; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/composite-filter-expr.ts: -------------------------------------------------------------------------------- 1 | import { FilterExpr, IFilterExpr } from "../.."; 2 | import { GraphObjDef } from "../../../cypher-types"; 3 | import { ISetOperations, setOperations } from "./boolean/set-operations"; 4 | 5 | export interface ICompositeFilterResult { 6 | results: GraphObjDef[][]; 7 | addResult(objs: GraphObjDef[]): ICompositeFilterResult; 8 | composedResult(): GraphObjDef[]; 9 | } 10 | 11 | export class CompositeFilterResult { 12 | combinedResults: GraphObjDef[] = []; 13 | matchedResults: GraphObjDef[] = []; 14 | booleanResults: GraphObjDef[] = []; 15 | results: GraphObjDef[][] = []; 16 | setOps: ISetOperations = setOperations; 17 | 18 | addResult(objs: GraphObjDef[]) { 19 | this.results.push(objs); 20 | return this; 21 | } 22 | 23 | flatResult(): GraphObjDef[] { 24 | return this.results.flat(); 25 | } 26 | 27 | composedResult(): GraphObjDef[] { 28 | return this.flatResult(); 29 | } 30 | } 31 | 32 | export interface ICompositeFilterExpr extends IFilterExpr { 33 | addFilter(filterExpr: IFilterExpr): ICompositeFilterExpr; 34 | } 35 | 36 | export class CompositeFilterExpr 37 | extends FilterExpr 38 | implements ICompositeFilterExpr 39 | { 40 | composedFilters: IFilterExpr[] = []; 41 | setOps: ISetOperations = setOperations; 42 | 43 | config(config: any = {}) { 44 | this.config = config; 45 | return this; 46 | } 47 | 48 | addFilter(filterExpr: IFilterExpr) { 49 | this.composedFilters.push(filterExpr); 50 | return this; 51 | } 52 | 53 | createReduceComposed(objs: GraphObjDef[]) { 54 | return ( 55 | acc: ICompositeFilterResult, 56 | filter: IFilterExpr 57 | ): ICompositeFilterResult => { 58 | const results = filter.runAll(objs); 59 | acc.addResult(results); 60 | return acc; 61 | }; 62 | } 63 | 64 | createCompositeResult() { 65 | return new CompositeFilterResult(); 66 | } 67 | 68 | get filtersToReduce() { 69 | return this.composedFilters; 70 | } 71 | 72 | runComposed(objs: GraphObjDef[]): GraphObjDef[] { 73 | if (!this.composedFilters || this.composedFilters.length === 0) { 74 | return []; 75 | } 76 | const reduceFn = this.createReduceComposed(objs); 77 | try { 78 | const result = this.filtersToReduce.reduce( 79 | reduceFn, 80 | this.createCompositeResult() 81 | ); 82 | return result.composedResult(); 83 | } catch (err) { 84 | return []; 85 | } 86 | } 87 | 88 | runAll(objs: GraphObjDef[]): GraphObjDef[] { 89 | return this.runComposed(objs); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/filter-expr.ts: -------------------------------------------------------------------------------- 1 | import { NodeCompareConfigObj } from "."; 2 | import { ICypherStrategy } from "../.."; 3 | import { IGraphObjApi } from "../../../../adapters"; 4 | import { Handler } from "../../../builder/handler"; 5 | import { GraphObjDef } from "../../../cypher-types"; 6 | import { IAliasFilterExpr } from "./alias-filter"; 7 | 8 | export type NodeMatchFn = (obj: any, config: NodeCompareConfigObj) => boolean; 9 | 10 | export interface IFilterResult { 11 | [key: string]: GraphObjDef[]; 12 | } 13 | 14 | export interface IFilterExpr { 15 | results: GraphObjDef[]; 16 | run(obj: any): GraphObjDef | undefined; 17 | runAll(objs: GraphObjDef[]): GraphObjDef[]; 18 | isTrue(): boolean; 19 | } 20 | 21 | export abstract class FilterExpr extends Handler { 22 | strategy?: ICypherStrategy; 23 | filter?: IAliasFilterExpr; 24 | alias: string; 25 | node?: any; 26 | aliasKey: string = "_"; 27 | results: GraphObjDef[] = []; 28 | 29 | constructor(config?: { alias: string }) { 30 | super(); 31 | this.alias = config ? config.alias : "_"; 32 | } 33 | 34 | setAliasedFilter(filter: IAliasFilterExpr) { 35 | this.filter = filter; 36 | return this; 37 | } 38 | 39 | setStrategy(strategy: ICypherStrategy) { 40 | this.strategy = strategy; 41 | return this; 42 | } 43 | 44 | get graphObjApi(): IGraphObjApi | undefined { 45 | return this.strategy && this.strategy.graphObjApi; 46 | } 47 | 48 | setAlias(alias: string) { 49 | this.alias = alias; 50 | return this; 51 | } 52 | 53 | setNode(node: any) { 54 | this.node = node; 55 | return this; 56 | } 57 | 58 | propValue(obj: any, propName: string) { 59 | return this.graphObjApi && this.graphObjApi.propValue(obj, propName); 60 | } 61 | 62 | nodeLabels(obj: any) { 63 | return this.graphObjApi ? this.graphObjApi.nodeLabels(obj) : []; 64 | } 65 | 66 | isTrue(): boolean { 67 | return !!this.results.length; 68 | } 69 | 70 | isValid() { 71 | return this.node; 72 | } 73 | 74 | isDefined(value: any) { 75 | return value !== undefined && value !== null; 76 | } 77 | 78 | runAll(objs: GraphObjDef[]): GraphObjDef[] { 79 | return objs.reduce((acc: GraphObjDef[], obj) => { 80 | const result = this.run(obj); 81 | if (result) { 82 | acc.push(result); 83 | } 84 | return acc; 85 | }, []); 86 | } 87 | 88 | run(obj: any): GraphObjDef | undefined { 89 | return obj; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/filter-result-converter.ts: -------------------------------------------------------------------------------- 1 | import { IFilterResult } from "."; 2 | import { emptyResults } from "../.."; 3 | import { IQueryResult } from "../../../cypher-types"; 4 | 5 | export interface IFilterResultConverter { 6 | toQueryResult(filtered: IFilterResult): IQueryResult; 7 | } 8 | 9 | export class FilterResultConverter implements IFilterResultConverter { 10 | result: IFilterResult = {}; 11 | 12 | constructor() {} 13 | 14 | filterKeyResultToQueryResult(acc: IQueryResult, key: string) { 15 | const keyResults = this.result[key]; 16 | // insert each keyResult at specific index in data 17 | keyResults.map((keyResult, i) => { 18 | acc.data[i] = acc.data[i] || []; 19 | acc.data[i].splice(i, 0, keyResult); 20 | }); 21 | acc.headers.push(key); 22 | return acc; 23 | } 24 | 25 | toQueryResult(filtered: IFilterResult): IQueryResult { 26 | const keys = Object.keys(filtered); 27 | return keys.reduce( 28 | this.filterKeyResultToQueryResult.bind(this), 29 | emptyResults() 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./alias-filter"; 2 | export * from "./filter-expr"; 3 | export * from "./composite-filter-expr"; 4 | export * from "./compose-one-filter-expr"; 5 | export * from "./node-pattern-expr"; 6 | export * from "./prop"; 7 | export * from "./label"; 8 | export * from "./boolean"; 9 | export * from "./filter-result-converter"; 10 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/label/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./node-label-compare-expr"; 2 | export * from "./node-labels-match-expr"; 3 | export * from "./node-labels-include-expr"; 4 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/label/node-label-compare-expr.ts: -------------------------------------------------------------------------------- 1 | import { GraphObjDef } from "../../../../cypher-types"; 2 | import { FilterExpr } from "../filter-expr"; 3 | 4 | export type NodeLabelMatchFn = ( 5 | obj: any, 6 | config: NodeLabelConfigObj 7 | ) => boolean; 8 | 9 | export type LabelCompareFn = ( 10 | labels: string[], 11 | compareLabel: string 12 | ) => boolean; 13 | 14 | export type NodeLabelConfigObj = { 15 | label: string; 16 | not?: boolean; 17 | }; 18 | 19 | export class NodeLabelCompareExpr extends FilterExpr { 20 | label: string = "*"; 21 | not?: boolean; 22 | 23 | setLabel(alias: string) { 24 | this.alias = alias; 25 | return this; 26 | } 27 | 28 | setNot(not: boolean) { 29 | this.not = not; 30 | return this; 31 | } 32 | 33 | config(configObj: NodeLabelConfigObj) { 34 | this.setLabel(configObj.label); 35 | this.setNot(!!configObj.not); 36 | return this; 37 | } 38 | 39 | nodeMatches(obj: any, fn: NodeLabelMatchFn): GraphObjDef | undefined { 40 | const { node, label } = this; 41 | const matches = fn(obj, { label }); 42 | return matches ? node : undefined; 43 | } 44 | 45 | isValid() { 46 | return this.label && this.label.trim().length; 47 | } 48 | 49 | runCompare(compareFn: NodeLabelMatchFn, obj: any): GraphObjDef | undefined { 50 | if (!this.isValid()) { 51 | return undefined; 52 | } 53 | return this.nodeMatches(obj, compareFn); 54 | } 55 | 56 | compareLabel(label: any, compareLabel: any): boolean { 57 | return label == compareLabel; 58 | } 59 | 60 | run(obj: any): GraphObjDef | undefined { 61 | return this.runCompareValue(obj, this.compareLabel); 62 | } 63 | 64 | runCompareValue( 65 | obj: any, 66 | compareLabelFn?: LabelCompareFn 67 | ): GraphObjDef | undefined { 68 | compareLabelFn = compareLabelFn || this.compareLabel; 69 | if (!compareLabelFn) { 70 | this.error("Missing compare label function"); 71 | return; 72 | } 73 | const compare = (config: NodeLabelConfigObj) => 74 | compareLabelFn ? compareLabelFn(this.nodeLabels(obj), this.label) : false; 75 | return this.runCompare(compare, obj); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/label/node-labels-include-expr.ts: -------------------------------------------------------------------------------- 1 | import { IAliasFilterExpr } from ".."; 2 | import { 3 | NodeLabelCompareExpr, 4 | NodeLabelConfigObj, 5 | } from "./node-label-compare-expr"; 6 | 7 | export const createNodeLabelsIncludeExpr = (configObj: NodeLabelConfigObj) => 8 | new NodeLabelsIncludeExpr().config(configObj); 9 | 10 | export class NodeLabelsIncludeExpr extends NodeLabelCompareExpr { 11 | compareValue(nodeLabels: string[], compareLabel: string): boolean { 12 | return this.not 13 | ? !this.compareEqual(nodeLabels, compareLabel) 14 | : this.compareEqual(nodeLabels, compareLabel); 15 | } 16 | 17 | compareEqual(nodeLabels: string[], compareLabel: string): boolean { 18 | return nodeLabels.includes(compareLabel); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/label/node-labels-match-expr.ts: -------------------------------------------------------------------------------- 1 | import { IAliasFilterExpr } from ".."; 2 | import { 3 | NodeLabelCompareExpr, 4 | NodeLabelConfigObj, 5 | } from "./node-label-compare-expr"; 6 | 7 | export const createNodeLabelMatchesExpr = (configObj: NodeLabelConfigObj) => 8 | new NodeLabelMatchesExpr().config(configObj); 9 | 10 | export class NodeLabelMatchesExpr extends NodeLabelCompareExpr { 11 | compareValue(nodeLabels: string[], compareLabel: string | RegExp): boolean { 12 | return this.not 13 | ? !this.compareEqual(nodeLabels, compareLabel) 14 | : this.compareEqual(nodeLabels, compareLabel); 15 | } 16 | 17 | compareEqual(nodeLabels: string[], compareLabel: string | RegExp): boolean { 18 | return !!nodeLabels.find((label) => label.match(compareLabel)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/node-pattern-expr.ts: -------------------------------------------------------------------------------- 1 | import { FilterExpr, IFilterResult } from "."; 2 | import { GraphObjDef } from "../../../cypher-types"; 3 | 4 | export class NodePatternExpr extends FilterExpr { 5 | pattern: any; 6 | 7 | config(pattern: any) { 8 | this.pattern = pattern; 9 | } 10 | 11 | matchPattern(obj: GraphObjDef) { 12 | return true; 13 | } 14 | 15 | filterObj(obj: GraphObjDef) { 16 | return this.matchPattern(obj); 17 | } 18 | 19 | runAll(objs: GraphObjDef[]): GraphObjDef[] { 20 | const filterFn = this.filterObj.bind(this); 21 | return objs.filter(filterFn); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/prop/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./node-prop-compare-expr"; 2 | export * from "./node-prop-eql-expr"; 3 | export * from "./node-prop-gt-expr"; 4 | export * from "./node-prop-lt-expr"; 5 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/prop/node-prop-compare-expr.ts: -------------------------------------------------------------------------------- 1 | import { NodeMatchFn } from ".."; 2 | import { GraphObjDef } from "../../../../cypher-types"; 3 | import { FilterExpr } from "../filter-expr"; 4 | 5 | export type PropValueCompareFn = (nodeVal: any, compareVal: any) => boolean; 6 | 7 | export type NodeCompareConfigObj = { 8 | propName: string; 9 | propValue: any; 10 | equal?: boolean; 11 | not?: boolean; 12 | }; 13 | 14 | export class NodePropCompareExpr extends FilterExpr { 15 | propName: string = "*"; 16 | propValue: any; 17 | equal?: boolean; 18 | not?: boolean; 19 | 20 | config(configObj: NodeCompareConfigObj) { 21 | this.setPropName(configObj.propName); 22 | this.setPropValue(configObj.propValue); 23 | this.setEqual(!!configObj.equal); 24 | this.setNot(!!configObj.not); 25 | return this; 26 | } 27 | 28 | setPropName(propName: string) { 29 | this.propName = propName; 30 | return this; 31 | } 32 | 33 | setPropValue(propValue: string) { 34 | this.propValue = propValue; 35 | return this; 36 | } 37 | 38 | setEqual(equal: boolean) { 39 | this.equal = equal; 40 | return this; 41 | } 42 | 43 | setNot(not: boolean) { 44 | this.not = not; 45 | return this; 46 | } 47 | 48 | nodeMatches(obj: any, fn: NodeMatchFn): GraphObjDef | undefined { 49 | const { propName, propValue } = this; 50 | const matches = fn(obj, { propName, propValue }); 51 | return matches ? obj : undefined; 52 | } 53 | 54 | isValid() { 55 | return ( 56 | this.propName && 57 | this.propName.trim().length && 58 | this.isDefined(this.propValue) 59 | ); 60 | } 61 | 62 | runCompare(obj: any, compareFn: NodeMatchFn): GraphObjDef | undefined { 63 | if (!this.isValid()) { 64 | return; 65 | } 66 | return this.nodeMatches(obj, compareFn); 67 | } 68 | 69 | compareValue(nodeVal: any, compareVal: any): boolean { 70 | return nodeVal == compareVal; 71 | } 72 | 73 | run(obj: any): GraphObjDef | undefined { 74 | return this.runCompareValue(obj, this.compareValue); 75 | } 76 | 77 | runCompareValue( 78 | obj: any, 79 | compareValueFn?: PropValueCompareFn 80 | ): GraphObjDef | undefined { 81 | compareValueFn = compareValueFn || this.compareValue; 82 | if (!compareValueFn) { 83 | this.error("Missing compare value function"); 84 | return; 85 | } 86 | return this.runCompare(obj, (config: NodeCompareConfigObj) => 87 | compareValueFn 88 | ? compareValueFn(this.propValue(obj, config.propName), config.propValue) 89 | : false 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/prop/node-prop-eql-expr.ts: -------------------------------------------------------------------------------- 1 | import { NodeCompareConfigObj } from "."; 2 | import { IAliasFilterExpr } from ".."; 3 | import { NodePropCompareExpr } from "./node-prop-compare-expr"; 4 | 5 | export const createNodePropEqlExpr = (configObj: NodeCompareConfigObj) => 6 | new NodePropEqlExpr().config(configObj); 7 | 8 | export class NodePropEqlExpr extends NodePropCompareExpr { 9 | compareValue(nodeVal: any, compareVal: any): boolean { 10 | return this.not 11 | ? !this.compareEqual(nodeVal, compareVal) 12 | : this.compareEqual(nodeVal, compareVal); 13 | } 14 | 15 | compareEqual(nodeVal: any, compareVal: any): boolean { 16 | return nodeVal === compareVal; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/prop/node-prop-gt-expr.ts: -------------------------------------------------------------------------------- 1 | import { NodeCompareConfigObj } from "."; 2 | import { IAliasFilterExpr } from ".."; 3 | import { NodePropCompareExpr } from "./node-prop-compare-expr"; 4 | 5 | export const createNodePropGtExpr = (configObj: NodeCompareConfigObj) => 6 | new NodePropGtExpr().config(configObj); 7 | 8 | export class NodePropGtExpr extends NodePropCompareExpr { 9 | compareValue(nodeVal: any, compareVal: any): boolean { 10 | return this.equal 11 | ? this.compareGtEqual(nodeVal, compareVal) 12 | : this.compareGt(nodeVal, compareVal); 13 | } 14 | 15 | compareGt(nodeVal: any, compareVal: any): boolean { 16 | return nodeVal > compareVal; 17 | } 18 | 19 | compareGtEqual(nodeVal: any, compareVal: any): boolean { 20 | return nodeVal >= compareVal; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/cypher/strategy/read/where/prop/node-prop-lt-expr.ts: -------------------------------------------------------------------------------- 1 | import { NodeCompareConfigObj } from "."; 2 | import { IAliasFilterExpr } from ".."; 3 | import { NodePropCompareExpr } from "./node-prop-compare-expr"; 4 | 5 | export const createNodePropLtExpr = (configObj: NodeCompareConfigObj) => 6 | new NodePropLtExpr().config(configObj); 7 | 8 | export class NodePropLtExpr extends NodePropCompareExpr { 9 | compareValue(nodeVal: any, compareVal: any): boolean { 10 | return this.equal 11 | ? this.compareLtEqual(nodeVal, compareVal) 12 | : this.compareLt(nodeVal, compareVal); 13 | } 14 | 15 | compareLt(nodeVal: any, compareVal: any): boolean { 16 | return nodeVal < compareVal; 17 | } 18 | 19 | compareLtEqual(nodeVal: any, compareVal: any): boolean { 20 | return nodeVal <= compareVal; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/cypher/strategy/strategy-handler.ts: -------------------------------------------------------------------------------- 1 | import { ICypherStrategy } from "."; 2 | import { Handler } from ".."; 3 | 4 | export interface IStrategyHandler { 5 | strategy: ICypherStrategy; 6 | } 7 | 8 | export class StrategyHandler extends Handler { 9 | strategy: ICypherStrategy; 10 | 11 | constructor(strategy: ICypherStrategy) { 12 | super(); 13 | this.strategy = strategy; 14 | } 15 | 16 | get strategyMap() { 17 | return this.strategy.strategyMap; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/cypher/strategy/strategy.ts: -------------------------------------------------------------------------------- 1 | import { Handler } from ".."; 2 | import { IFilterExpr, IGraphApi, IGraphObjApi } from "../.."; 3 | import { GraphObjDef, IQueryResult } from "../cypher-types"; 4 | import { 5 | IQueryController, 6 | QueryController, 7 | IMatchController, 8 | IWhereController, 9 | IReturnController, 10 | IResultController, 11 | } from "./controllers"; 12 | import { IBaseController } from "./controllers/base-controller"; 13 | import { defaultStrategyMap } from "./defaults"; 14 | import { IStrategyMap } from "./map"; 15 | 16 | export interface ICypherStrategy { 17 | strategyMap: IStrategyMap; 18 | graphApi?: IGraphApi; 19 | graphObjApi?: IGraphObjApi; 20 | queryController: IQueryController; 21 | match: IMatchController; 22 | where: IWhereController; 23 | return: IReturnController; 24 | result: IResultController; 25 | 26 | latestExpr?: IFilterExpr; 27 | addAsExpression( 28 | clauseName: string, 29 | key: string, 30 | config: any 31 | ): ICypherStrategy; 32 | setGraphApi(graphApi: IGraphApi): ICypherStrategy; 33 | configure(config: any): ICypherStrategy; 34 | run(objs: GraphObjDef[]): IQueryResult; 35 | } 36 | 37 | export class CypherStrategy extends Handler implements ICypherStrategy { 38 | graphApi?: IGraphApi; 39 | graphObjApi?: IGraphObjApi; 40 | queryController: IQueryController = new QueryController(this); 41 | strategyMap: IStrategyMap = defaultStrategyMap(); 42 | 43 | get latestExpr() { 44 | return this.queryController.latestExpr; 45 | } 46 | 47 | get controllers() { 48 | return this.queryController.controllers; 49 | } 50 | 51 | get match() { 52 | return this.controllers["match"]; 53 | } 54 | 55 | get where() { 56 | return this.controllers["where"]; 57 | } 58 | 59 | get return() { 60 | return this.controllers["return"]; 61 | } 62 | 63 | get result() { 64 | return this.controllers["result"]; 65 | } 66 | 67 | addAsExpression( 68 | clauseName: string, 69 | key: string, 70 | config: any 71 | ): ICypherStrategy { 72 | const controller: IBaseController = this.controllers[clauseName]; 73 | controller.addAsExpression(key, config); 74 | return this; 75 | } 76 | 77 | setGraphApi(api: IGraphApi) { 78 | this.graphApi = api; 79 | return this; 80 | } 81 | 82 | setGraphObjApi(api: IGraphObjApi) { 83 | this.graphObjApi = api; 84 | return this; 85 | } 86 | 87 | configure(config: any) { 88 | this.setGraphApi(config.graphApi); 89 | return this; 90 | } 91 | 92 | run(objs: GraphObjDef[]): IQueryResult { 93 | const controller = this.queryController; 94 | const { matchExec, whereExec, returnExec } = controller as any; 95 | let aliasMap: any; 96 | if (matchExec) { 97 | aliasMap = matchExec.filter(objs); 98 | } 99 | if (whereExec) { 100 | whereExec.configure({ aliasMap }); 101 | aliasMap = whereExec.filter(objs); 102 | } 103 | if (!returnExec) { 104 | } 105 | if (!aliasMap) { 106 | } 107 | return returnExec.process(aliasMap) as IQueryResult; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/cypher/strategy/types.ts: -------------------------------------------------------------------------------- 1 | interface IStrategy { 2 | run(): any; 3 | } 4 | -------------------------------------------------------------------------------- /src/cypher/strategy/util.ts: -------------------------------------------------------------------------------- 1 | export class NodeUtils { 2 | propValue(node: any, propName: string) { 3 | return node["__props"][propName]; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/hello-world.ts: -------------------------------------------------------------------------------- 1 | import Gun from "gun"; 2 | import { GunAPI, LocalGraph, SelectTrav, TripTrav } from "./adapters/gun"; 3 | 4 | import { QueryFind, QuerySearch } from "./adapters/gun/search"; 5 | 6 | import {} from "./adapters/gun/graph-api"; 7 | 8 | export function sayHello() { 9 | console.log("hi"); 10 | } 11 | export function sayGoodbye() { 12 | console.log("goodbye"); 13 | } 14 | 15 | // create a Gun instance using Gun('localhost') or Gun('127.0.0.1') 16 | export const tryQueries = (gun: any) => { 17 | const schema = new GunAPI(gun); 18 | const triple = { subject: "?p", predicate: "type", object: "Artist" }; 19 | const trav = new TripTrav(triple); 20 | QuerySearch.search(schema, trav); 21 | 22 | const selection = { name: "Ice Cream" }; 23 | const travSel = new SelectTrav(selection); 24 | QueryFind.find(schema, travSel); 25 | 26 | const lGraph = new LocalGraph(); 27 | const test = lGraph.add({ id: "test1" }); 28 | const obj = { 29 | name: "test", 30 | label: "person", 31 | birthdate: "07-05-1986", 32 | link: test, 33 | }; 34 | const test0 = lGraph.add(obj); 35 | }; 36 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./cypher"; 2 | export * from "./adapters"; 3 | export * from "./types"; 4 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { IGunChainReference } from "gun/types/chain"; 2 | 3 | type ArrayOf = T extends Array ? U : never; 4 | type ArrayAsRecord = ArrayOf extends never 5 | ? DataType 6 | : Record; 7 | 8 | export interface IPromisedChain extends IGunChainReference { 9 | promise< 10 | TResult1 = { 11 | data: any; 12 | put: ArrayAsRecord; 13 | key: any; 14 | gun: IGunChainReference; 15 | } 16 | >( 17 | onfulfilled?: (value: TResult1) => TResult1 | PromiseLike 18 | ): Promise; 19 | } 20 | -------------------------------------------------------------------------------- /test/cypher/builder/read/match/match-clause-builder.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/builder/read/match/match-clause-builder.test.ts -------------------------------------------------------------------------------- /test/cypher/builder/read/match/match-obj-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, createMatchObjExprBuilder } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("MatchObjExprBuilder", () => { 6 | let strategy, exprBuilder; 7 | beforeEach(() => { 8 | strategy = new CypherStrategy(); 9 | }); 10 | 11 | describe("strategy expr", () => { 12 | let expr; 13 | beforeEach(() => { 14 | exprBuilder = createMatchObjExprBuilder(strategy, { alias: "a" }); 15 | expr = exprBuilder.expr; 16 | }); 17 | 18 | it("is an expression", () => { 19 | expect(expr).toBeDefined(); 20 | expect(expr.name).toEqual("obj"); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/cypher/builder/read/result/limit-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, createLimitExprBuilder } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("LimitExprBuilder", () => { 6 | let strategy, exprBuilder; 7 | beforeEach(() => { 8 | strategy = new CypherStrategy(); 9 | }); 10 | 11 | describe("strategy expr", () => { 12 | let expr; 13 | beforeEach(() => { 14 | exprBuilder = createLimitExprBuilder(strategy, { by: 5 }); 15 | expr = exprBuilder.expr; 16 | }); 17 | 18 | it("is a limit expression", () => { 19 | expect(expr).toBeDefined(); 20 | expect(expr.name).toEqual("limit"); 21 | }); 22 | 23 | describe("by", () => { 24 | it("set limit by", () => { 25 | exprBuilder.by(2); 26 | expect(expr.number).toEqual(2); 27 | }); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/cypher/builder/read/result/result-clause-builder.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/builder/read/result/result-clause-builder.test.ts -------------------------------------------------------------------------------- /test/cypher/builder/read/result/skip-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, createSkipExprBuilder } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("SkipExprBuilder", () => { 6 | let strategy; 7 | beforeEach(() => { 8 | strategy = new CypherStrategy(); 9 | }); 10 | 11 | describe("strategy expr", () => { 12 | let exprBuilder, expr; 13 | beforeEach(() => { 14 | exprBuilder = createSkipExprBuilder(strategy, { by: 5 }); 15 | expr = exprBuilder.expr; 16 | }); 17 | 18 | it("is a skip expression", () => { 19 | expect(expr).toBeDefined(); 20 | expect(expr.name).toEqual("skip"); 21 | }); 22 | 23 | describe("by", () => { 24 | it("set skip by", () => { 25 | exprBuilder.by(2); 26 | expect(expr.count).toEqual(2); 27 | }); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/cypher/builder/read/result/union-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, createUnionExprBuilder } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("UnionExprBuilder", () => { 6 | let strategy, expr; 7 | beforeEach(() => { 8 | strategy = new CypherStrategy(); 9 | expr = createUnionExprBuilder(strategy, {}); 10 | }); 11 | 12 | describe("by", () => { 13 | it("set union with", () => { 14 | const resultSet = {}; 15 | expr.with(resultSet); 16 | expect(expr).toBeDefined(); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/cypher/builder/read/return/aggregation-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CypherStrategy, 3 | createReturnAggregationExprBuilder, 4 | } from "../../../../../src"; 5 | 6 | const context = describe; 7 | 8 | describe("AggregationExprBuilder", () => { 9 | let strategy, expr; 10 | beforeEach(() => { 11 | strategy = new CypherStrategy(); 12 | expr = createReturnAggregationExprBuilder(strategy, {}); 13 | }); 14 | 15 | describe("matches", () => { 16 | it("adds sum expression", () => { 17 | expr.sum("salary"); 18 | expect(expr).toBeDefined(); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/cypher/builder/read/return/count-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CypherStrategy, 3 | createReturnCountExprBuilder, 4 | } from "../../../../../src"; 5 | 6 | const context = describe; 7 | 8 | describe("CountExprBuilder", () => { 9 | let strategy, exprBuilder, expr; 10 | beforeEach(() => { 11 | strategy = new CypherStrategy(); 12 | exprBuilder = createReturnCountExprBuilder(strategy, { alias: "animals" }); 13 | expr = exprBuilder.expr; 14 | }); 15 | 16 | describe("distinct", () => { 17 | it("count distinct animals", () => { 18 | exprBuilder.distinct(); 19 | expect(expr).toBeDefined(); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/cypher/builder/read/return/prop-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CypherStrategy, 3 | createReturnPropExprBuilder, 4 | } from "../../../../../src"; 5 | 6 | const context = describe; 7 | 8 | describe("PropExprBuilder", () => { 9 | let strategy, exprBuilder, expr; 10 | beforeEach(() => { 11 | strategy = new CypherStrategy(); 12 | exprBuilder = createReturnPropExprBuilder(strategy, {}); 13 | expr = exprBuilder.expr; 14 | }); 15 | 16 | describe("city", () => { 17 | it("city", () => { 18 | exprBuilder.prop("city"); 19 | expect(expr).toBeDefined(); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/cypher/builder/read/return/return-clause-builder.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/builder/read/return/return-clause-builder.test.ts -------------------------------------------------------------------------------- /test/cypher/builder/read/where/boolean/and-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, AndExprBuilder } from "../../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("AndExprBuilder", () => { 6 | let strategy, expr; 7 | beforeEach(() => { 8 | strategy = new CypherStrategy(); 9 | expr = new AndExprBuilder(strategy); 10 | }); 11 | 12 | describe("matches", () => { 13 | it("adds expressions under and expr", () => { 14 | expr.matches({ city: "amsterdam" }).matches({ age: { gte: 18 } }); 15 | expect(expr).toBeDefined(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/cypher/builder/read/where/boolean/not-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, NotExprBuilder } from "../../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("NotExprBuilder", () => { 6 | let strategy, expr; 7 | beforeEach(() => { 8 | strategy = new CypherStrategy(); 9 | expr = new NotExprBuilder(strategy); 10 | }); 11 | 12 | describe("matches", () => { 13 | it("adds expressions under not expr", () => { 14 | expr.matches({ city: "amsterdam" }).matches({ age: { gte: 18 } }); 15 | expect(expr).toBeDefined(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/cypher/builder/read/where/boolean/or-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, OrExprBuilder } from "../../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("OrExprBuilder", () => { 6 | let strategy, expr; 7 | beforeEach(() => { 8 | strategy = new CypherStrategy(); 9 | expr = new OrExprBuilder(strategy); 10 | }); 11 | 12 | describe("matches", () => { 13 | it("adds expressions under or expr", () => { 14 | expr.matches({ city: "amsterdam" }).matches({ age: { gte: 18 } }); 15 | expect(expr).toBeDefined(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/cypher/builder/read/where/label-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, createLabelExprBuilder } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("LabelExprBuilder", () => { 6 | let strategy, expr; 7 | beforeEach(() => { 8 | strategy = new CypherStrategy(); 9 | expr = createLabelExprBuilder(strategy, {}); 10 | }); 11 | 12 | describe("city", () => { 13 | it("city", () => { 14 | expr.label("city"); 15 | expect(expr).toBeDefined(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/cypher/builder/read/where/prop-expr-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, createPropExprBuilder } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("PropExprBuilder", () => { 6 | let strategy, expr; 7 | beforeEach(() => { 8 | strategy = new CypherStrategy(); 9 | expr = createPropExprBuilder(strategy, {}); 10 | }); 11 | 12 | describe("city", () => { 13 | it("city", () => { 14 | expr.prop("city"); 15 | expect(expr).toBeDefined(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/cypher/builder/read/where/where-clause-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, WhereClauseBuilder } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("WhereClauseBuilder", () => { 6 | let strategy, clause; 7 | beforeEach(() => { 8 | strategy = new CypherStrategy(); 9 | clause = new WhereClauseBuilder(strategy); 10 | }); 11 | 12 | describe("or", () => { 13 | it("creates or clause", () => { 14 | const expr = clause.or; 15 | expect(expr).toBeDefined(); 16 | }); 17 | }); 18 | 19 | describe("and", () => { 20 | it("creates and clause", () => { 21 | const expr = clause.and; 22 | expect(expr).toBeDefined(); 23 | }); 24 | }); 25 | 26 | describe("not", () => { 27 | it("creates not clause", () => { 28 | const expr = clause.not; 29 | expect(expr).toBeDefined(); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/cypher/builder/write/create/create.test.ts: -------------------------------------------------------------------------------- 1 | import { Query, GunAPI } from "../../../../../src"; 2 | import Gun from "gun"; 3 | 4 | const context = describe; 5 | 6 | describe("Query", () => { 7 | let query, ctx, gun; 8 | beforeEach(() => { 9 | gun = Gun(); 10 | ctx = new GunAPI(gun); 11 | query = new Query(ctx); 12 | }); 13 | 14 | const aliasKeysFor = (query, name = "alias") => 15 | Object.keys(query.aliasMap[name]); 16 | const keyFor = (res) => keysFor(res)[0]; 17 | const keysFor = (res) => Object.keys(res); 18 | const labelsFor = (alias, res) => res[alias]["__labels"]; 19 | const labelFor = (alias, res) => labelsFor(alias, res)[0]; 20 | const propsFor = (alias, res) => res[alias]["__props"]; 21 | 22 | describe("create", () => { 23 | describe("node", () => { 24 | it("creates node with an alias", () => { 25 | const alias = "X"; 26 | query.create.node(alias); 27 | }); 28 | 29 | it("creates anonymous node", () => { 30 | const alias = "X"; 31 | const label = "x"; 32 | const result = query.create.node({}); 33 | expect(keyFor(result)).toEqual("_"); 34 | }); 35 | 36 | it("creates node with a label", () => { 37 | const alias = "X"; 38 | const label = "x"; 39 | const result = query.create.node({ alias, label }); 40 | expect(keysFor(result)).toEqual(["X"]); 41 | expect(labelFor("X", result)).toEqual(label); 42 | }); 43 | 44 | it("creates node with a label and props", () => { 45 | const alias = "X"; 46 | const label = "x"; 47 | const props = { x: 2 }; 48 | const result = query.create.node({ alias, label, props }); 49 | expect(keysFor(result)).toEqual(["X"]); 50 | expect(labelFor("X", result)).toEqual(label); 51 | expect(propsFor("X", result)).toEqual(props); 52 | }); 53 | 54 | it("creates node with multiple labels", () => { 55 | const alias = "X"; 56 | const labels = ["x", "xx"]; 57 | const result = query.create.node({ alias, labels }); 58 | expect(keysFor(result)).toEqual(["X"]); 59 | expect(labelsFor("X", result)).toEqual(labels); 60 | }); 61 | 62 | it("creates node with a single label when supplying single and multiple labels", () => { 63 | const alias = "X"; 64 | const label = "x"; 65 | const labels = ["x", "xx"]; 66 | const result = query.create.node({ alias, labels }); 67 | expect(keyFor(result)).toEqual("X"); 68 | expect(labelsFor("X", result)).toEqual([label]); 69 | }); 70 | }); 71 | 72 | describe("nodes", () => { 73 | it("creates multiple nodes", () => { 74 | const xlabels = ["x", "xx"]; 75 | const nodes = [ 76 | { 77 | alias: "X", 78 | labels: xlabels, 79 | props: { x: 1 }, 80 | }, 81 | { 82 | alias: "Y", 83 | label: "y", 84 | props: { y: 2 }, 85 | }, 86 | ]; 87 | const result = query.$create.nodes(nodes); 88 | expect(keyFor(result)).toEqual(["X", "Y"]); 89 | expect(labelsFor("X", result)).toEqual(xlabels); 90 | expect(labelFor("Y", result)).toEqual("y"); 91 | }); 92 | }); 93 | 94 | context("node relationships", () => { 95 | const sourceNode = { 96 | alias: "X", 97 | label: "x", 98 | }; 99 | const targetNode = { 100 | alias: "Y", 101 | label: "y", 102 | }; 103 | 104 | const friendRelation = { 105 | alias: "FRIEND", 106 | label: "friend", 107 | }; 108 | 109 | describe("relation", () => { 110 | it("creates nodes with a relationship between them", () => { 111 | query.create.relation(sourceNode, friendRelation, targetNode); 112 | }); 113 | }); 114 | 115 | describe("relationTo", () => { 116 | it("creates nodes with a directed relationship from source to target node", () => { 117 | query.create.relationTo(sourceNode, friendRelation, targetNode); 118 | }); 119 | }); 120 | 121 | describe("relationFrom", () => { 122 | it("creates nodes with a directed relationship from target to source node", () => { 123 | query.create.relationFrom(sourceNode, friendRelation, targetNode); 124 | }); 125 | }); 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/cypher/builder/write/delete/delete.test.ts: -------------------------------------------------------------------------------- 1 | import { Query, GunAPI } from "../../../../../src"; 2 | import Gun from "gun"; 3 | 4 | describe("Query", () => { 5 | let query, ctx, gun; 6 | beforeEach(() => { 7 | gun = Gun(); 8 | ctx = new GunAPI(gun); 9 | query = new Query(ctx); 10 | }); 11 | 12 | const keyFor = (res) => keysFor(res)[0]; 13 | const keysFor = (res) => Object.keys(res); 14 | 15 | describe("delete", () => { 16 | describe("node", () => { 17 | it("deletes node with an alias", () => { 18 | const alias = "X"; 19 | const node = query.create.node(alias); 20 | const result = query.delete.node(alias); 21 | expect(keyFor(result)).toEqual("X"); 22 | }); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/cypher/builder/write/load/csv.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/builder/write/load/csv.test.ts -------------------------------------------------------------------------------- /test/cypher/fixtures/cars.ts: -------------------------------------------------------------------------------- 1 | const mazda = { 2 | type: "node", 3 | labels: ["car"], 4 | props: { brand: "mazda" }, 5 | }; 6 | 7 | const audi = { 8 | type: "node", 9 | labels: ["car"], 10 | props: { brand: "audi" }, 11 | }; 12 | 13 | export { mazda, audi }; 14 | -------------------------------------------------------------------------------- /test/cypher/fixtures/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./cars"; 2 | export * from "./owns"; 3 | export * from "./persons"; 4 | -------------------------------------------------------------------------------- /test/cypher/fixtures/owns.ts: -------------------------------------------------------------------------------- 1 | import { michael, anna } from "./persons"; 2 | import { mazda, audi } from "./cars"; 3 | 4 | const michael_owns_audi = { 5 | type: "edge", 6 | labels: ["owns"], 7 | props: { since: 2016 }, 8 | from: michael, 9 | to: audi, 10 | }; 11 | 12 | const anna_owns_mazda = { 13 | type: "edge", 14 | labels: ["owns"], 15 | props: { since: 2012 }, 16 | from: anna, 17 | to: mazda, 18 | }; 19 | 20 | export { michael_owns_audi, anna_owns_mazda }; 21 | -------------------------------------------------------------------------------- /test/cypher/fixtures/persons.ts: -------------------------------------------------------------------------------- 1 | const michael = { 2 | type: "node", 3 | labels: ["person"], 4 | props: { name: "michael", age: 36, sex: "male" }, 5 | }; 6 | 7 | const anna = { 8 | type: "node", 9 | labels: ["person"], 10 | props: { name: "anna", age: 27, sex: "female" }, 11 | }; 12 | 13 | const thomas = { 14 | type: "node", 15 | labels: ["person"], 16 | props: { name: "thomas", age: 17, sex: "male" }, 17 | }; 18 | 19 | export { michael, anna }; 20 | -------------------------------------------------------------------------------- /test/cypher/query.test.ts: -------------------------------------------------------------------------------- 1 | import { Query, GunAPI } from "../../src"; 2 | import Gun from "gun"; 3 | 4 | describe("Query", () => { 5 | let query, ctx, gun; 6 | beforeEach(() => { 7 | gun = Gun(); 8 | ctx = new GunAPI(gun); 9 | query = new Query(ctx); 10 | }); 11 | 12 | const aliasKeysFor = (query, name = "alias") => 13 | Object.keys(query.aliasMap[name]); 14 | const keyFor = (res) => keysFor(res)[0]; 15 | const keysFor = (res) => Object.keys(res); 16 | const labelsFor = (alias, res) => res[alias]["__labels"]; 17 | const labelFor = (alias, res) => labelsFor(alias, res)[0]; 18 | const propsFor = (alias, res) => res[alias]["__props"]; 19 | 20 | describe("create", () => { 21 | it("has a node method", () => { 22 | expect(query.create.node).toBeDefined(); 23 | }); 24 | 25 | it("has a nodes method", () => { 26 | expect(query.create.nodes).toBeDefined(); 27 | }); 28 | }); 29 | 30 | describe("match", () => { 31 | it("has a nodes method", () => { 32 | expect(query.match.nodes).toBeDefined(); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/cypher/strategy/clauses/match-clause.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClauseType, 3 | AliasFilterExpr, 4 | CypherStrategy, 5 | MatchClause, 6 | } from "../../../../src"; 7 | 8 | const context = describe; 9 | 10 | describe("MatchClause", () => { 11 | let strategy, clause; 12 | beforeEach(() => { 13 | strategy = new CypherStrategy(); 14 | clause = new MatchClause(strategy); 15 | }); 16 | 17 | describe("createExpression", () => { 18 | it("creates expression", () => { 19 | const config = {}; 20 | const expr = clause.createExpression("x", config); 21 | expect(expr).toBeDefined(); 22 | }); 23 | }); 24 | 25 | describe("addAsExpression", () => { 26 | context("single valid expression", () => { 27 | it("creates and adds expression", () => { 28 | const config = {}; 29 | const expr = clause.addAsExpression("x", config); 30 | expect(expr).toBeDefined(); 31 | }); 32 | }); 33 | }); 34 | 35 | describe("addExpressions", () => { 36 | context("single valid expression", () => { 37 | it("adds expressions", () => { 38 | const config = {}; 39 | const expr = clause.createExpression("obj", config); 40 | clause.addExpressions(expr); 41 | expect(clause.current).toBe(expr); 42 | }); 43 | }); 44 | }); 45 | 46 | describe("setAliasFilterExpr", () => { 47 | it("sets alias filter expression", () => { 48 | const aliasFilterExpr = new AliasFilterExpr(); 49 | clause.setAliasFilterExpr(aliasFilterExpr); 50 | expect(clause.aliasFilterExpr).toBe(aliasFilterExpr); 51 | }); 52 | }); 53 | 54 | describe("type", () => { 55 | it("is match", () => { 56 | expect(clause.type).toBe(ClauseType.match); 57 | }); 58 | }); 59 | 60 | describe("typeName", () => { 61 | it("is match", () => { 62 | expect(clause.typeName).toBe("match"); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/cypher/strategy/clauses/match-clauses.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CypherStrategy, 3 | MatchClause, 4 | MatchClauses, 5 | // CypherStrategy, 6 | } from "../../../../src"; 7 | 8 | const context = describe; 9 | 10 | describe("MatchClauses", () => { 11 | let strategy, clauses; 12 | 13 | const createClauses = () => new MatchClauses(strategy); 14 | const createClause = () => new MatchClause(strategy); 15 | 16 | beforeEach(() => { 17 | strategy = new CypherStrategy(); 18 | clauses = createClauses(); 19 | }); 20 | 21 | describe("addClause", () => { 22 | context("valid clause", () => { 23 | it("adds a single clause", () => { 24 | const clause = createClause(); 25 | clauses.addClause(clause); 26 | expect(clauses.count).toEqual(1); 27 | }); 28 | }); 29 | }); 30 | 31 | describe("isValid", () => { 32 | context("valid where clause", () => { 33 | it("adds a single clause", () => { 34 | const clause = createClause(); 35 | const valid = clauses.isValid(clause); 36 | expect(valid).toBeTruthy(); 37 | }); 38 | }); 39 | }); 40 | 41 | describe("optional and must", () => { 42 | beforeEach(() => { 43 | const clause = createClause(); 44 | clauses.addClause(clause); 45 | }); 46 | 47 | context("added single valid where clause", () => { 48 | it("has no optional clauses", () => { 49 | expect(clauses.optional.length).toEqual(0); 50 | }); 51 | 52 | it("has one must clause", () => { 53 | expect(clauses.must.length).toEqual(1); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/cypher/strategy/clauses/result-clause.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AliasFilterExpr, 3 | CypherStrategy, 4 | ResultClause, 5 | ClauseType, 6 | } from "../../../../src"; 7 | 8 | const context = describe; 9 | 10 | describe("ResultClause", () => { 11 | let strategy, clause; 12 | beforeEach(() => { 13 | strategy = new CypherStrategy(); 14 | clause = new ResultClause(strategy); 15 | }); 16 | 17 | describe("createExpression", () => { 18 | it("creates expression", () => { 19 | const config = {}; 20 | const expr = clause.createExpression("x", config); 21 | expect(expr).toBeDefined(); 22 | }); 23 | }); 24 | 25 | describe("addAsExpression", () => { 26 | context("single valid expression", () => { 27 | it("creates and adds expression", () => { 28 | const config = {}; 29 | const expr = clause.addAsExpression("x", config); 30 | expect(expr).toBeDefined(); 31 | }); 32 | }); 33 | }); 34 | 35 | describe("addExpressions", () => { 36 | context("single valid expression", () => { 37 | it("adds expressions", () => { 38 | const config = {}; 39 | const expr = clause.createExpression("obj", config); 40 | clause.addExpressions(expr); 41 | expect(clause.current).toBe(expr); 42 | }); 43 | }); 44 | }); 45 | 46 | describe("setAliasFilterExpr", () => { 47 | it("sets alias filter expression", () => { 48 | const aliasFilterExpr = new AliasFilterExpr(); 49 | clause.setAliasFilterExpr(aliasFilterExpr); 50 | expect(clause.aliasFilterExpr).toBe(aliasFilterExpr); 51 | }); 52 | }); 53 | 54 | describe("type", () => { 55 | it("is match", () => { 56 | expect(clause.type).toBe(ClauseType.return); 57 | }); 58 | }); 59 | 60 | describe("typeName", () => { 61 | it("is match", () => { 62 | expect(clause.typeName).toBe("match"); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/cypher/strategy/clauses/result-clauses.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, ResultClause, ResultClauses } from "../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("ResultClauses", () => { 6 | let strategy, clauses; 7 | 8 | const createClauses = () => new ResultClauses(strategy); 9 | const createClause = () => new ResultClause(strategy); 10 | 11 | beforeEach(() => { 12 | strategy = new CypherStrategy(); 13 | clauses = createClauses(); 14 | }); 15 | 16 | describe("addClause", () => { 17 | context("valid clause", () => { 18 | it("adds a single clause", () => { 19 | const clause = createClause(); 20 | clauses.addClause(clause); 21 | expect(clauses.count).toEqual(1); 22 | }); 23 | }); 24 | }); 25 | 26 | describe("isValid", () => { 27 | context("valid where clause", () => { 28 | it("adds a single clause", () => { 29 | const clause = createClause(); 30 | const valid = clauses.isValid(clause); 31 | expect(valid).toBeTruthy(); 32 | }); 33 | }); 34 | }); 35 | 36 | describe("optional and must", () => { 37 | beforeEach(() => { 38 | const clause = createClause(); 39 | clauses.addClause(clause); 40 | }); 41 | 42 | context("added single valid where clause", () => { 43 | it("has no optional clauses", () => { 44 | expect(clauses.optional.length).toEqual(0); 45 | }); 46 | 47 | it("has one must clause", () => { 48 | expect(clauses.must.length).toEqual(1); 49 | }); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/cypher/strategy/clauses/return-clause.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AliasFilterExpr, 3 | CypherStrategy, 4 | ReturnClause, 5 | ClauseType, 6 | } from "../../../../src"; 7 | 8 | const context = describe; 9 | 10 | describe("ReturnClause", () => { 11 | let strategy, clause; 12 | beforeEach(() => { 13 | strategy = new CypherStrategy(); 14 | clause = new ReturnClause(strategy); 15 | }); 16 | 17 | describe("createExpression", () => { 18 | it("creates expression", () => { 19 | const config = {}; 20 | const expr = clause.createExpression("x", config); 21 | expect(expr).toBeDefined(); 22 | }); 23 | }); 24 | 25 | describe("addAsExpression", () => { 26 | context("single valid expression", () => { 27 | it("creates and adds expression", () => { 28 | const config = {}; 29 | const expr = clause.addAsExpression("x", config); 30 | expect(expr).toBeDefined(); 31 | }); 32 | }); 33 | }); 34 | 35 | describe("addExpressions", () => { 36 | context("single valid expression", () => { 37 | it("adds expressions", () => { 38 | const config = {}; 39 | const expr = clause.createExpression("obj", config); 40 | clause.addExpressions(expr); 41 | expect(clause.current).toBe(expr); 42 | }); 43 | }); 44 | }); 45 | 46 | describe("setAliasFilterExpr", () => { 47 | it("sets alias filter expression", () => { 48 | const aliasFilterExpr = new AliasFilterExpr(); 49 | clause.setAliasFilterExpr(aliasFilterExpr); 50 | expect(clause.aliasFilterExpr).toBe(aliasFilterExpr); 51 | }); 52 | }); 53 | 54 | describe("type", () => { 55 | it("is match", () => { 56 | expect(clause.type).toBe(ClauseType.return); 57 | }); 58 | }); 59 | 60 | describe("typeName", () => { 61 | it("is match", () => { 62 | expect(clause.typeName).toBe("match"); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/cypher/strategy/clauses/return-clauses.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, ReturnClause, ReturnClauses } from "../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("ReturnClauses", () => { 6 | let strategy, clauses; 7 | 8 | const createClauses = () => new ReturnClauses(strategy); 9 | const createClause = () => new ReturnClause(strategy); 10 | 11 | beforeEach(() => { 12 | strategy = new CypherStrategy(); 13 | clauses = createClauses(); 14 | }); 15 | 16 | describe("addClause", () => { 17 | context("valid clause", () => { 18 | it("adds a single clause", () => { 19 | const clause = createClause(); 20 | clauses.addClause(clause); 21 | expect(clauses.count).toEqual(1); 22 | }); 23 | }); 24 | }); 25 | 26 | describe("isValid", () => { 27 | context("valid where clause", () => { 28 | it("adds a single clause", () => { 29 | const clause = createClause(); 30 | const valid = clauses.isValid(clause); 31 | expect(valid).toBeTruthy(); 32 | }); 33 | }); 34 | }); 35 | 36 | describe("optional and must", () => { 37 | beforeEach(() => { 38 | const clause = createClause(); 39 | clauses.addClause(clause); 40 | }); 41 | 42 | context("added single valid where clause", () => { 43 | it("has no optional clauses", () => { 44 | expect(clauses.optional.length).toEqual(0); 45 | }); 46 | 47 | it("has one must clause", () => { 48 | expect(clauses.must.length).toEqual(1); 49 | }); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/cypher/strategy/clauses/where-clause.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CypherStrategy, 3 | WhereClause, 4 | ClauseType, 5 | AliasFilterExpr, 6 | FilterExpr, 7 | NodeLabelMatchesExpr, 8 | WhereClauses, 9 | } from "../../../../src"; 10 | 11 | const context = describe; 12 | 13 | describe("WhereClause", () => { 14 | let strategy, clause; 15 | beforeEach(() => { 16 | strategy = new CypherStrategy(); 17 | clause = new WhereClause(strategy); 18 | }); 19 | 20 | describe("type", () => { 21 | it("is match", () => { 22 | expect(clause.type).toBe(ClauseType.match); 23 | }); 24 | }); 25 | 26 | describe("typeName", () => { 27 | it("is match", () => { 28 | expect(clause.typeName).toBe("match"); 29 | }); 30 | }); 31 | 32 | describe("current", () => { 33 | context("no expressions", () => { 34 | it("is undefined", () => { 35 | expect(clause.typeName).toBe("match"); 36 | }); 37 | }); 38 | 39 | context("one expression", () => { 40 | it("is last expression", () => { 41 | const expr1 = new NodeLabelMatchesExpr(); 42 | clause.addExpressions(expr1); 43 | expect(clause.current).toBe(expr1); 44 | }); 45 | }); 46 | 47 | context("two expressions", () => { 48 | it("is last expression", () => { 49 | const expr1 = new NodeLabelMatchesExpr(); 50 | const expr2 = new NodeLabelMatchesExpr(); 51 | clause.addExpressions(expr1, expr2); 52 | expect(clause.current).toBe(expr2); 53 | }); 54 | }); 55 | }); 56 | 57 | describe("map", () => { 58 | it("is a map", () => { 59 | expect(clause.map).toBeDefined(); 60 | }); 61 | }); 62 | 63 | describe("setContainer", () => { 64 | it("sets container and map will be defined", () => { 65 | const container = new WhereClauses(strategy); 66 | clause.setContainer(container); 67 | expect(clause.map).toBeDefined(); 68 | }); 69 | }); 70 | 71 | describe("findMatchingMapKey", () => { 72 | context("bad key", () => { 73 | it("no key found", () => { 74 | const found = clause.findMatchingMapKey("bad"); 75 | expect(found).toBeFalsy(); 76 | }); 77 | }); 78 | 79 | context("existing key", () => { 80 | it("finds key", () => { 81 | const found = clause.findMatchingMapKey("and"); 82 | expect(found).toBeTruthy(); 83 | }); 84 | }); 85 | }); 86 | 87 | describe("findMatchingMapKey", () => { 88 | context("bad key", () => { 89 | it("no sub map found", () => { 90 | const exprMap = clause.findExprMapForKey("bad"); 91 | expect(exprMap).toBe(clause.map); 92 | }); 93 | }); 94 | 95 | context("existing key", () => { 96 | it("sub map found", () => { 97 | const exprMap = clause.findExprMapForKey("bad"); 98 | expect(exprMap).toBeDefined(); 99 | expect(exprMap).not.toBe(clause.map); 100 | }); 101 | }); 102 | }); 103 | 104 | describe("createExpression", () => { 105 | it("creates expression", () => { 106 | const config = {}; 107 | const expr = clause.createExpression("x", config); 108 | expect(expr).toBeDefined(); 109 | }); 110 | }); 111 | 112 | describe("addAsExpression", () => { 113 | context("single valid expression", () => { 114 | it("creates and adds expression", () => { 115 | const config = {}; 116 | const expr = clause.addAsExpression("x", config); 117 | expect(expr).toBeDefined(); 118 | }); 119 | }); 120 | }); 121 | 122 | describe("addExpressions", () => { 123 | context("single valid expression", () => { 124 | it("adds expressions", () => { 125 | const config = {}; 126 | const expr = clause.createExpression("obj", config); 127 | clause.addExpressions(expr); 128 | expect(clause.current).toBe(expr); 129 | }); 130 | }); 131 | }); 132 | 133 | describe("setAliasFilterExpr", () => { 134 | it("sets alias filter expression", () => { 135 | const aliasFilterExpr = new AliasFilterExpr(); 136 | clause.setAliasFilterExpr(aliasFilterExpr); 137 | expect(clause.aliasFilterExpr).toBe(aliasFilterExpr); 138 | }); 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /test/cypher/strategy/clauses/where-clauses.test.ts: -------------------------------------------------------------------------------- 1 | import { CypherStrategy, WhereClause, WhereClauses } from "../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("WhereClauses", () => { 6 | let strategy, clauses; 7 | 8 | const createClauses = () => new WhereClauses(strategy); 9 | const createClause = () => new WhereClause(strategy); 10 | 11 | beforeEach(() => { 12 | strategy = new CypherStrategy(); 13 | clauses = createClauses(); 14 | }); 15 | 16 | describe("addClause", () => { 17 | context("valid where clause", () => { 18 | it("adds a single clause", () => { 19 | const clause = createClause(); 20 | clauses.addClause(clause); 21 | expect(clauses.count).toEqual(1); 22 | }); 23 | }); 24 | }); 25 | 26 | describe("isValid", () => { 27 | context("valid clause", () => { 28 | it("adds a single clause", () => { 29 | const clause = createClause(); 30 | const valid = clauses.isValid(clause); 31 | expect(valid).toBeTruthy(); 32 | }); 33 | }); 34 | }); 35 | 36 | describe("optional and must", () => { 37 | beforeEach(() => { 38 | const clause = createClause(); 39 | clauses.addClause(clause); 40 | }); 41 | 42 | context("added single valid where clause", () => { 43 | it("has no optional clauses", () => { 44 | expect(clauses.optional.length).toEqual(0); 45 | }); 46 | 47 | it("has one must clause", () => { 48 | expect(clauses.must.length).toEqual(1); 49 | }); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/cypher/strategy/controllers/match-controller.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AliasFilterExpr, 3 | CypherStrategy, 4 | MatchClause, 5 | MatchController, 6 | MatchObjExpr, 7 | } from "../../../../src"; 8 | 9 | const context = describe; 10 | 11 | describe("MatchController", () => { 12 | let strategy, controller; 13 | 14 | // const createClauses = () => new MatchClauses(strategy); 15 | const createClause = () => new MatchClause(strategy); 16 | const createController = () => new MatchController(strategy); 17 | 18 | beforeEach(() => { 19 | strategy = new CypherStrategy(); 20 | controller = createController(); 21 | }); 22 | 23 | describe("clauses", () => { 24 | it("has clauses", () => { 25 | expect.any(controller.clauses.length).toEqual(0); 26 | }); 27 | }); 28 | 29 | describe("addClause", () => { 30 | beforeEach(() => { 31 | const clause = createClause(); 32 | controller.addClause(clause); 33 | }); 34 | 35 | it("clauses has a single clause", () => { 36 | expect.any(controller.clauses.length).toEqual(1); 37 | }); 38 | context("single clause", () => { 39 | describe("setAliasFilterExpr", () => { 40 | it("adds expression to clause", () => { 41 | const aliasExpr = new AliasFilterExpr(); 42 | controller.setAliasFilterExpr(aliasExpr); 43 | const { current } = controller.clauses; 44 | expect.any(current.aliasFilterExpr).toBe(aliasExpr); 45 | }); 46 | }); 47 | 48 | describe("addExpressions", () => { 49 | it("adds expression to clause", () => { 50 | const expr = new MatchObjExpr(); 51 | controller.addExpressions(expr); 52 | const { current } = controller.clauses; 53 | expect.any(current.expressions.length).toEqual(1); 54 | }); 55 | }); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/cypher/strategy/controllers/query-controller.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | QueryController, 3 | // CypherStrategy, 4 | } from "../../../../src"; 5 | 6 | const context = describe; 7 | 8 | describe("QueryController", () => {}); 9 | -------------------------------------------------------------------------------- /test/cypher/strategy/controllers/result-controller.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AliasFilterExpr, 3 | CypherStrategy, 4 | ResultClause, 5 | ResultController, 6 | ReturnCountExpr, 7 | } from "../../../../src"; 8 | 9 | const context = describe; 10 | 11 | describe("ResultController", () => { 12 | let strategy, controller; 13 | 14 | // const createClauses = () => new MatchClauses(strategy); 15 | const createClause = () => new ResultClause(strategy); 16 | const createController = () => new ResultController(strategy); 17 | 18 | beforeEach(() => { 19 | strategy = new CypherStrategy(); 20 | controller = createController(); 21 | }); 22 | 23 | describe("clauses", () => { 24 | it("has clauses", () => { 25 | expect.any(controller.clauses.length).toEqual(0); 26 | }); 27 | }); 28 | 29 | describe("addClause", () => { 30 | beforeEach(() => { 31 | const clause = createClause(); 32 | controller.addClause(clause); 33 | }); 34 | 35 | it("clauses has a single clause", () => { 36 | expect.any(controller.clauses.length).toEqual(1); 37 | }); 38 | context("single clause", () => { 39 | describe("setAliasFilterExpr", () => { 40 | it("adds expression to clause", () => { 41 | const aliasExpr = new AliasFilterExpr(); 42 | controller.setAliasFilterExpr(aliasExpr); 43 | const { current } = controller.clauses; 44 | expect.any(current.aliasFilterExpr).toBe(aliasExpr); 45 | }); 46 | }); 47 | 48 | describe("addExpressions", () => { 49 | it("adds expression to clause", () => { 50 | const expr = new ReturnCountExpr(); 51 | controller.addExpressions(expr); 52 | const { current } = controller.clauses; 53 | expect.any(current.expressions.length).toEqual(1); 54 | }); 55 | }); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/cypher/strategy/controllers/return-controller.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AliasFilterExpr, 3 | CypherStrategy, 4 | ReturnClause, 5 | ReturnController, 6 | } from "../../../../src"; 7 | 8 | const context = describe; 9 | 10 | describe("ReturnController", () => { 11 | let strategy, controller; 12 | 13 | // const createClauses = () => new MatchClauses(strategy); 14 | const createClause = () => new ReturnClause(strategy); 15 | const createController = () => new ReturnController(strategy); 16 | 17 | beforeEach(() => { 18 | strategy = new CypherStrategy(); 19 | controller = createController(); 20 | }); 21 | 22 | describe("clauses", () => { 23 | it("has clauses", () => { 24 | expect.any(controller.clauses.length).toEqual(0); 25 | }); 26 | }); 27 | 28 | describe("addClause", () => { 29 | beforeEach(() => { 30 | const clause = createClause(); 31 | controller.addClause(clause); 32 | }); 33 | 34 | it("clauses has a single clause", () => { 35 | expect.any(controller.clauses.length).toEqual(1); 36 | }); 37 | context("single clause", () => { 38 | describe("setAliasFilterExpr", () => { 39 | it("adds expression to clause", () => { 40 | const aliasExpr = new AliasFilterExpr(); 41 | controller.setAliasFilterExpr(aliasExpr); 42 | const { current } = controller.clauses; 43 | expect.any(current.aliasFilterExpr).toBe(aliasExpr); 44 | }); 45 | }); 46 | 47 | describe("addExpressions", () => { 48 | it("adds expression to clause", () => { 49 | const expr = new MatchObjExpr(); 50 | controller.addExpressions(expr); 51 | const { current } = controller.clauses; 52 | expect.any(current.expressions.length).toEqual(1); 53 | }); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/cypher/strategy/controllers/where-controller.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AliasFilterExpr, 3 | CypherStrategy, 4 | WhereClause, 5 | WhereController, 6 | } from "../../../../src"; 7 | 8 | const context = describe; 9 | 10 | describe("WhereController", () => { 11 | let strategy, controller; 12 | 13 | // const createClauses = () => new MatchClauses(strategy); 14 | const createClause = () => new WhereClause(strategy); 15 | const createController = () => new WhereController(strategy); 16 | 17 | beforeEach(() => { 18 | strategy = new CypherStrategy(); 19 | controller = createController(); 20 | }); 21 | 22 | describe("clauses", () => { 23 | it("has clauses", () => { 24 | expect.any(controller.clauses.length).toEqual(0); 25 | }); 26 | }); 27 | 28 | describe("addClause", () => { 29 | beforeEach(() => { 30 | const clause = createClause(); 31 | controller.addClause(clause); 32 | }); 33 | 34 | it("clauses has a single clause", () => { 35 | expect.any(controller.clauses.length).toEqual(1); 36 | }); 37 | context("single clause", () => { 38 | describe("setAliasFilterExpr", () => { 39 | it("adds expression to clause", () => { 40 | const aliasExpr = new AliasFilterExpr(); 41 | controller.setAliasFilterExpr(aliasExpr); 42 | const { current } = controller.clauses; 43 | expect.any(current.aliasFilterExpr).toBe(aliasExpr); 44 | }); 45 | }); 46 | 47 | describe("addExpressions", () => { 48 | it("adds expression to clause", () => { 49 | const expr = new MatchObjExpr(); 50 | controller.addExpressions(expr); 51 | const { current } = controller.clauses; 52 | expect.any(current.expressions.length).toEqual(1); 53 | }); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/match/match-obj-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { MatchObjExpr, createMatchObjExpr } from "../../../../../src"; 2 | 3 | import { audi, michael } from "../../../fixtures"; 4 | 5 | const context = describe; 6 | 7 | describe("MatchObjExpr", () => { 8 | let expr: MatchObjExpr; 9 | let config: any; 10 | 11 | // { alias: "p", label: "person" } 12 | context("person: michael", () => { 13 | beforeEach(() => { 14 | config = { 15 | alias: "p", 16 | labels: ["person"], 17 | props: {}, 18 | }; 19 | expr = createMatchObjExpr(config); 20 | }); 21 | 22 | let map; 23 | describe("runAll", () => { 24 | beforeEach(() => { 25 | map = { 26 | persons: [michael, audi], 27 | }; 28 | }); 29 | it("michael is a person", () => { 30 | const result = expr.runAll(map.persons); 31 | expect(result).toEqual([michael]); 32 | }); 33 | 34 | it("audi is not a person", () => { 35 | const result = expr.runAll([audi]); 36 | expect(result).toEqual([undefined]); 37 | }); 38 | }); 39 | 40 | describe("run", () => { 41 | it("michael is a person", () => { 42 | const result = expr.run(michael); 43 | expect(result).toEqual(michael); 44 | }); 45 | 46 | it("audi is not a person", () => { 47 | const result = expr.run(audi); 48 | expect(result).toEqual(undefined); 49 | }); 50 | }); 51 | }); 52 | 53 | context("car: audi", () => { 54 | beforeEach(() => { 55 | config = { 56 | alias: "c", 57 | labels: ["audi"], 58 | props: {}, 59 | }; 60 | expr = createMatchObjExpr(config); 61 | }); 62 | 63 | describe("run", () => { 64 | it("michael is not a car", () => { 65 | const result = expr.run(michael); 66 | expect(result).toEqual(undefined); 67 | }); 68 | 69 | it("audi is not a car", () => { 70 | const result = expr.run(audi); 71 | expect(result).toEqual(audi); 72 | }); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/result/group-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OrderExpr, 3 | // CypherStrategy, 4 | } from "../../../../src"; 5 | 6 | const context = describe; 7 | 8 | describe("OrderExpr", () => {}); 9 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/result/limit-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { createResults, LimitExpr } from "../../../../src"; 2 | 3 | describe("LimitExpr", () => { 4 | let expr; 5 | beforeEach(() => { 6 | expr = new LimitExpr(); 7 | }); 8 | 9 | describe("setLimit(5)", () => { 10 | beforeEach(() => { 11 | expr.setLimit(5); 12 | }); 13 | it("sets num to 5", () => { 14 | expect(expr.num).toEqual(5); 15 | }); 16 | 17 | describe("run with 10 data rows", () => { 18 | beforeEach(() => { 19 | expr.setResults(createResults(10)); 20 | }); 21 | it("limits results to 5 rows", () => { 22 | expr.run(); 23 | expect(expr.rows).toEqual(5); 24 | expect(expr.columns).toEqual(10); 25 | expect(expr.data[0]).toEqual({ id: 0 }); 26 | expect(expr.data[4]).toEqual({ id: 4 }); 27 | }); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/result/order-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OrderExpr, 3 | // CypherStrategy, 4 | } from "../../../../src"; 5 | 6 | const context = describe; 7 | 8 | describe("OrderExpr", () => {}); 9 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/result/skip-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { createResults, SkipExpr } from "../../../../src"; 2 | 3 | describe("SkipExpr", () => { 4 | let expr; 5 | beforeEach(() => { 6 | expr = new SkipExpr(); 7 | }); 8 | 9 | describe("setSkip(5)", () => { 10 | beforeEach(() => { 11 | expr.setSkip(5); 12 | }); 13 | it("sets num to 5", () => { 14 | expect(expr.num).toEqual(5); 15 | }); 16 | 17 | describe("run with 10 data rows", () => { 18 | beforeEach(() => { 19 | expr.setResults(createResults(10)); 20 | }); 21 | it("skips first 5 rows", () => { 22 | expr.run(); 23 | expect(expr.rows).toEqual(5); 24 | expect(expr.columns).toEqual(10); 25 | expect(expr.data[0]).toEqual({ id: 5 }); 26 | expect(expr.data[4]).toEqual({ id: 9 }); 27 | }); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/result/union-expr.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/strategy/read/result/union-expr.test.ts -------------------------------------------------------------------------------- /test/cypher/strategy/read/return/aggregation-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { ReturnAggregationExpr } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("ReturnAggregationExpr", () => { 6 | // invalid function 7 | // sum 8 | // avg 9 | }); 10 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/return/count-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { ReturnCountExpr } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("ReturnCountExpr", () => {}); 6 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/return/label-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { ReturnLabelExpr } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("ReturnLabelExpr", () => {}); 6 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/return/prop-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { ReturnPropExpr } from "../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("ReturnPropExpr", () => {}); 6 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/alias-filter-expr.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/strategy/read/where/alias-filter-expr.test.ts -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/boolean/and/and-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphologyObjApi, 3 | IGraphObjApi, 4 | NodePropGtExpr, 5 | NodePropEqlExpr, 6 | AndFilterExpr, 7 | createAndFilterExpr, 8 | // CypherStrategy, 9 | } from "../../../../../../src"; 10 | 11 | const context = describe; 12 | 13 | describe("AndFilterExpr", () => { 14 | let expr: AndFilterExpr; 15 | let api: any; 16 | let configObj: any; 17 | let filter; 18 | let graphObjApi: IGraphObjApi; 19 | 20 | const female30 = { props: { sex: "female", age: 30 } }; 21 | const male14 = { props: { sex: "male", age: 14 } }; 22 | const male21 = { props: { sex: "male", age: 21 } }; 23 | const male17 = { props: { sex: "male", age: 17 } }; 24 | 25 | beforeEach(() => { 26 | api = {}; 27 | graphObjApi = new GraphologyObjApi(); // 28 | // strategy = new CypherStrategy(); 29 | configObj = { 30 | node: {}, 31 | propName: "a", 32 | propValue: "1", 33 | }; 34 | expr = createAndFilterExpr(configObj); 35 | }); 36 | 37 | describe("compareValue: 5", () => { 38 | let adultFilter = new NodePropGtExpr(filter).config({ 39 | propName: "age", 40 | propValue: 18, 41 | equal: true, 42 | }); 43 | let maleFilter = new NodePropEqlExpr(filter).config({ 44 | propName: "sex", 45 | propValue: "male", 46 | }); 47 | 48 | context("NO males > 18", () => { 49 | beforeEach(() => { 50 | expr.matchedResults = [female30, male14, male17]; 51 | expr.addFilter(adultFilter); 52 | expr.addFilter(maleFilter); 53 | }); 54 | it("returns empty result", () => { 55 | const result = expr.run(); 56 | expect(result.length).toBe(0); 57 | }); 58 | }); 59 | 60 | context("one male > 18", () => { 61 | beforeEach(() => { 62 | expr.matchedResults = [female30, male14, male21]; 63 | expr.addFilter(adultFilter); 64 | expr.addFilter(maleFilter); 65 | }); 66 | it("returns single male over 18", () => { 67 | const result = expr.run(); 68 | expect(result.length).toBe(1); 69 | expect(result).toEqual([{}]); 70 | }); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/boolean/and/and-filter-results.test.ts: -------------------------------------------------------------------------------- 1 | import { AndCompositeFilterResult } from "../../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("AndCompositeFilterResult", () => { 6 | let fr: AndCompositeFilterResult; 7 | 8 | const female30 = { props: { sex: "female", age: 30 } }; 9 | const male14 = { props: { sex: "male", age: 14 } }; 10 | const male21 = { props: { sex: "male", age: 21 } }; 11 | const male17 = { props: { sex: "male", age: 17 } }; 12 | 13 | beforeEach(() => { 14 | fr = new AndCompositeFilterResult(); 15 | }); 16 | 17 | context("no matched results", () => { 18 | describe("addResult", () => { 19 | beforeAll(() => { 20 | fr.addResult([female30]); 21 | }); 22 | 23 | it("adds result", () => { 24 | expect(fr.results).toEqual([female30]); 25 | }); 26 | }); 27 | 28 | describe("flatResult", () => { 29 | beforeAll(() => { 30 | fr.addResult([female30]); 31 | fr.addResult([male21]); 32 | }); 33 | 34 | it("returns flat result", () => { 35 | expect(fr.flatResult()).toEqual([female30, male21]); 36 | }); 37 | }); 38 | }); 39 | 40 | context("some matched results", () => {}); 41 | }); 42 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/boolean/not/not-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | StrategyFilter, 3 | GraphologyObjApi, 4 | IGraphObjApi, 5 | createNotFilterExpr, 6 | NotFilterExpr, 7 | NodePropGtExpr, 8 | } from "../../../../../../src"; 9 | 10 | const context = describe; 11 | 12 | describe("NotFilterExpr", () => { 13 | let expr: NotFilterExpr; 14 | let api: any; 15 | let configObj: any; 16 | let filter; 17 | let graphObjApi: IGraphObjApi; 18 | 19 | const female30 = { props: { sex: "female", age: 30 } }; 20 | const male14 = { props: { sex: "male", age: 14 } }; 21 | const male21 = { props: { sex: "male", age: 21 } }; 22 | const male17 = { props: { sex: "male", age: 17 } }; 23 | 24 | beforeEach(() => { 25 | api = {}; 26 | graphObjApi = new GraphologyObjApi(); // 27 | filter = new StrategyFilter(graphObjApi); 28 | configObj = { 29 | node: {}, 30 | propName: "a", 31 | propValue: "1", 32 | }; 33 | expr = createNotFilterExpr(filter, configObj); 34 | }); 35 | 36 | describe("compareValue: 5", () => { 37 | let adultFilter = new NodePropGtExpr(filter).config({ 38 | propName: "age", 39 | propValue: 18, 40 | equal: true, 41 | }); 42 | 43 | context("NO males > 18", () => { 44 | beforeEach(() => { 45 | expr.matchedResults = [female30, male14, male17]; 46 | expr.setComposedFilter(adultFilter); 47 | }); 48 | it("returns all 3 results", () => { 49 | const result = expr.run(); 50 | expect(result.length).toBe(3); 51 | expect(result).toEqual([female30, male14, male17]); 52 | }); 53 | }); 54 | 55 | context("one male > 18", () => { 56 | beforeEach(() => { 57 | expr.matchedResults = [female30, male14, male21]; 58 | expr.setComposedFilter(adultFilter); 59 | }); 60 | it("returns all except for male over 18", () => { 61 | const result = expr.run(); 62 | expect(result.length).toBe(2); 63 | expect(result).toEqual([female30, male14]); 64 | }); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/boolean/not/not-filter-results.test.ts: -------------------------------------------------------------------------------- 1 | import { NotCompositeFilterResult } from "../../../../../../src"; 2 | 3 | const context = describe; 4 | 5 | describe("NotCompositeFilterResult", () => { 6 | let fr: NotCompositeFilterResult; 7 | 8 | const female30 = { props: { sex: "female", age: 30 } }; 9 | const male14 = { props: { sex: "male", age: 14 } }; 10 | const male21 = { props: { sex: "male", age: 21 } }; 11 | const male17 = { props: { sex: "male", age: 17 } }; 12 | 13 | beforeEach(() => { 14 | fr = new NotCompositeFilterResult(); 15 | }); 16 | 17 | context("no matched results", () => { 18 | describe("addResult", () => { 19 | beforeAll(() => { 20 | fr.addResult([female30]); 21 | }); 22 | 23 | it("adds result", () => { 24 | expect(fr.results).toEqual([female30]); 25 | }); 26 | }); 27 | 28 | describe("flatResult", () => { 29 | beforeAll(() => { 30 | fr.addResult([female30]); 31 | fr.addResult([male21]); 32 | }); 33 | 34 | it("returns flat result", () => { 35 | expect(fr.flatResult()).toEqual([female30, male21]); 36 | }); 37 | }); 38 | }); 39 | 40 | context("some matched results", () => {}); 41 | }); 42 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/boolean/or/or-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | StrategyFilter, 3 | GraphologyObjApi, 4 | IGraphObjApi, 5 | NodePropGtExpr, 6 | NodePropEqlExpr, 7 | OrFilterExpr, 8 | createOrFilterExpr, 9 | } from "../../../../../../src"; 10 | 11 | const context = describe; 12 | 13 | describe("OrFilterExpr", () => { 14 | let expr: OrFilterExpr; 15 | let api: any; 16 | let configObj: any; 17 | let filter; 18 | let graphObjApi: IGraphObjApi; 19 | 20 | const female30 = { props: { sex: "female", age: 30 } }; 21 | const male14 = { props: { sex: "male", age: 14 } }; 22 | const male21 = { props: { sex: "male", age: 21 } }; 23 | const male17 = { props: { sex: "male", age: 17 } }; 24 | 25 | beforeEach(() => { 26 | api = {}; 27 | graphObjApi = new GraphologyObjApi(); // 28 | filter = new StrategyFilter(graphObjApi); 29 | configObj = { 30 | node: {}, 31 | propName: "a", 32 | propValue: "1", 33 | }; 34 | expr = createOrFilterExpr(filter, configObj); 35 | }); 36 | 37 | describe("compareValue: 5", () => { 38 | let adultFilter = new NodePropGtExpr(filter).config({ 39 | propName: "age", 40 | propValue: 18, 41 | equal: true, 42 | }); 43 | let maleFilter = new NodePropEqlExpr(filter).config({ 44 | propName: "sex", 45 | propValue: "male", 46 | }); 47 | 48 | context("two males but NO males > 18", () => { 49 | beforeEach(() => { 50 | expr.matchedResults = [female30, male14, male17]; 51 | expr.addFilter(adultFilter); 52 | expr.addFilter(maleFilter); 53 | }); 54 | it("returns 2 males", () => { 55 | const result = expr.run(); 56 | expect(result.length).toBe(2); 57 | expect(result).toEqual([male14, male17]); 58 | }); 59 | }); 60 | 61 | context("one male > 18", () => { 62 | beforeEach(() => { 63 | expr.matchedResults = [female30, male14, male21]; 64 | expr.addFilter(adultFilter); 65 | expr.addFilter(maleFilter); 66 | }); 67 | it("returns single male over 18", () => { 68 | const result = expr.run(); 69 | expect(result.length).toBe(1); 70 | expect(result).toEqual([male14, male21]); 71 | }); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/boolean/or/or-filter-results.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | StrategyFilter, 3 | GraphologyObjApi, 4 | IGraphObjApi, 5 | OrCompositeFilterResult, 6 | } from "../../../../../../src"; 7 | 8 | const context = describe; 9 | 10 | describe("OrCompositeFilterResult", () => { 11 | let fr: OrCompositeFilterResult; 12 | 13 | const female30 = { props: { sex: "female", age: 30 } }; 14 | const male14 = { props: { sex: "male", age: 14 } }; 15 | const male21 = { props: { sex: "male", age: 21 } }; 16 | const male17 = { props: { sex: "male", age: 17 } }; 17 | 18 | beforeEach(() => { 19 | fr = new OrCompositeFilterResult(); 20 | }); 21 | 22 | context("no matched results", () => { 23 | describe("addResult", () => { 24 | beforeAll(() => { 25 | fr.addResult([female30]); 26 | }); 27 | 28 | it("adds result", () => { 29 | expect(fr.results).toEqual([female30]); 30 | }); 31 | }); 32 | 33 | describe("flatResult", () => { 34 | beforeAll(() => { 35 | fr.addResult([female30]); 36 | fr.addResult([male21]); 37 | }); 38 | 39 | it("returns flat result", () => { 40 | expect(fr.flatResult()).toEqual([female30, male21]); 41 | }); 42 | }); 43 | }); 44 | 45 | context("some matched results", () => {}); 46 | }); 47 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/boolean/set-operations.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/strategy/read/where/boolean/set-operations.test.ts -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/compose-one-filter-expr.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/strategy/read/where/compose-one-filter-expr.test.ts -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/composite-filter-expr.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/strategy/read/where/composite-filter-expr.test.ts -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/filter-expr.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/strategy/read/where/filter-expr.test.ts -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/filter-result-converter.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/strategy/read/where/filter-result-converter.test.ts -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/label/node-labels-include-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createNodeLabelsIncludeExpr, 3 | NodeLabelsIncludeExpr, 4 | StrategyFilter, 5 | GraphologyObjApi, 6 | IGraphObjApi, 7 | } from "../../../../../src"; 8 | 9 | describe("NodeLabelsIncludeExpr", () => { 10 | let expr: NodeLabelsIncludeExpr; 11 | let api: any; 12 | let configObj: any; 13 | let filter; 14 | let graphObjApi: IGraphObjApi; 15 | beforeEach(() => { 16 | api = {}; 17 | graphObjApi = new GraphologyObjApi(); // 18 | filter = new StrategyFilter(graphObjApi); 19 | configObj = { 20 | node: {}, 21 | propName: "a", 22 | propValue: "1", 23 | }; 24 | expr = createNodeLabelsIncludeExpr(filter, configObj); 25 | }); 26 | 27 | describe("compareValue: 5", () => { 28 | describe("eql", () => { 29 | beforeEach(() => { 30 | expr.setLabel("a").setNot(false); 31 | }); 32 | it("a to a - true", () => { 33 | const result = expr.compareValue(["a"], "a"); 34 | expect(result).toBeTruthy(); 35 | }); 36 | 37 | it("a to b - false", () => { 38 | const result = expr.compareValue(["a"], "b"); 39 | expect(result).toBeFalsy(); 40 | }); 41 | }); 42 | 43 | describe("not", () => { 44 | beforeEach(() => { 45 | expr.setLabel("a").setNot(true); 46 | }); 47 | it("a to a - false", () => { 48 | const result = expr.compareValue(["a"], "a"); 49 | expect(result).toBeFalsy(); 50 | }); 51 | 52 | it("a to b - true", () => { 53 | const result = expr.compareValue(["a"], "b"); 54 | expect(result).toBeTruthy(); 55 | }); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/label/node-labels-match-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createNodeLabelMatchesExpr, 3 | NodeLabelMatchesExpr, 4 | StrategyFilter, 5 | GraphologyObjApi, 6 | } from "../../../../../src"; 7 | 8 | describe("NodeLabelMatchesExpr", () => { 9 | let expr: NodeLabelMatchesExpr; 10 | let api: any; 11 | let configObj: any; 12 | let graphObjApi, filter; 13 | beforeEach(() => { 14 | api = {}; 15 | graphObjApi = new GraphologyObjApi(); // 16 | filter = new StrategyFilter(graphObjApi); 17 | configObj = { 18 | node: {}, 19 | propName: "a", 20 | propValue: "1", 21 | }; 22 | expr = createNodeLabelMatchesExpr(filter, configObj); 23 | }); 24 | 25 | describe("compareValue: 5", () => { 26 | describe("eql", () => { 27 | beforeEach(() => { 28 | expr.setLabel("a").setNot(false); 29 | }); 30 | it("a to a - true", () => { 31 | const result = expr.compareValue(["a"], /a/); 32 | expect(result).toBeTruthy(); 33 | }); 34 | 35 | it("a to b - false", () => { 36 | const result = expr.compareValue(["a"], "b"); 37 | expect(result).toBeFalsy(); 38 | }); 39 | }); 40 | 41 | describe("not", () => { 42 | beforeEach(() => { 43 | expr.setLabel("a").setNot(true); 44 | }); 45 | it("a to a - false", () => { 46 | const result = expr.compareValue(["a"], "a"); 47 | expect(result).toBeFalsy(); 48 | }); 49 | 50 | it("a to b - true", () => { 51 | const result = expr.compareValue(["a"], /b/); 52 | expect(result).toBeTruthy(); 53 | }); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/node-pattern-expr.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianmandrup/cypher-query/ce417601e1874086b6fd90407f9693afbd503f0e/test/cypher/strategy/read/where/node-pattern-expr.test.ts -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/prop/node-prop-eql-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createNodePropEqlExpr, 3 | GraphologyObjApi, 4 | StrategyFilter, 5 | } from "../../../../../src"; 6 | 7 | describe("NodePropEqlExpr", () => { 8 | let expr; 9 | let api: any; 10 | let configObj: any; 11 | let graphObjApi, filter; 12 | beforeEach(() => { 13 | api = {}; 14 | graphObjApi = new GraphologyObjApi(); // 15 | filter = new StrategyFilter(graphObjApi); 16 | configObj = { 17 | node: {}, 18 | propName: "a", 19 | propValue: "1", 20 | }; 21 | expr = createNodePropEqlExpr(filter, configObj); 22 | }); 23 | 24 | describe("compareValue: 5", () => { 25 | describe("eql", () => { 26 | beforeEach(() => { 27 | expr.setPropValue(5).setNot(false); 28 | }); 29 | it("5 to 5 - true", () => { 30 | const result = expr.compareValue(5, 5); 31 | expect(result).toBeTruthy(); 32 | }); 33 | 34 | it("5 to 4 - false", () => { 35 | const result = expr.compareValue(5, 4); 36 | expect(result).toBeFalsy(); 37 | }); 38 | }); 39 | 40 | describe("not", () => { 41 | beforeEach(() => { 42 | expr.setPropValue(5).setNot(true); 43 | }); 44 | it("5 to 5 - true", () => { 45 | const result = expr.compareValue(5, 5); 46 | expect(result).toBeFalsy(); 47 | }); 48 | 49 | it("5 to 4 - false", () => { 50 | const result = expr.compareValue(5, 4); 51 | expect(result).toBeTruthy(); 52 | }); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/prop/node-prop-gt-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createNodePropGtExpr, 3 | GraphologyObjApi, 4 | StrategyFilter, 5 | } from "../../../../../src"; 6 | 7 | describe("NodePropGtExpr", () => { 8 | let expr; 9 | let api: any; 10 | let configObj: any; 11 | let graphObjApi, filter; 12 | beforeEach(() => { 13 | api = {}; 14 | graphObjApi = new GraphologyObjApi(); // 15 | filter = new StrategyFilter(graphObjApi); 16 | configObj = { 17 | node: {}, 18 | propName: "a", 19 | propValue: "1", 20 | }; 21 | expr = createNodePropGtExpr(filter, configObj); 22 | }); 23 | 24 | describe("compareValue: 5", () => { 25 | describe("not equal", () => { 26 | beforeEach(() => { 27 | expr.setPropValue(5).setEqual(false); 28 | }); 29 | it("5 to 5 - true", () => { 30 | const result = expr.compareValue(5, 5); 31 | expect(result).toBeTruthy(); 32 | }); 33 | 34 | it("5 to 4 - false", () => { 35 | const result = expr.compareValue(5, 4); 36 | expect(result).toBeFalsy(); 37 | }); 38 | }); 39 | 40 | describe("equal", () => { 41 | beforeEach(() => { 42 | expr.setPropValue(5).setEqual(true); 43 | }); 44 | it("5 to 5 - true", () => { 45 | const result = expr.compareValue(5, 5); 46 | expect(result).toBeFalsy(); 47 | }); 48 | 49 | it("5 to 4 - false", () => { 50 | const result = expr.compareValue(5, 4); 51 | expect(result).toBeTruthy(); 52 | }); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/cypher/strategy/read/where/prop/node-prop-lt-expr.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createNodePropLtExpr, 3 | GraphologyObjApi, 4 | NodePropLtExpr, 5 | StrategyFilter, 6 | } from "../../../../../src"; 7 | 8 | describe("NodePropLtExpr", () => { 9 | let expr; 10 | let api: any; 11 | let configObj: any; 12 | let graphObjApi, filter; 13 | beforeEach(() => { 14 | api = {}; 15 | graphObjApi = new GraphologyObjApi(); // 16 | filter = new StrategyFilter(graphObjApi); 17 | configObj = { 18 | node: {}, 19 | propName: "a", 20 | propValue: "1", 21 | }; 22 | expr = createNodePropLtExpr(filter, configObj); 23 | }); 24 | 25 | describe("compareValue: 5", () => { 26 | describe("not equal", () => { 27 | beforeEach(() => { 28 | expr.setPropValue(5).setEqual(false); 29 | }); 30 | it("5 to 5 - true", () => { 31 | const result = expr.compareValue(5, 5); 32 | expect(result).toBeTruthy(); 33 | }); 34 | 35 | it("5 to 4 - false", () => { 36 | const result = expr.compareValue(5, 4); 37 | expect(result).toBeFalsy(); 38 | }); 39 | }); 40 | 41 | describe("equal", () => { 42 | beforeEach(() => { 43 | expr.setPropValue(5).setEqual(true); 44 | }); 45 | it("5 to 5 - true", () => { 46 | const result = expr.compareValue(5, 5); 47 | expect(result).toBeFalsy(); 48 | }); 49 | 50 | it("5 to 4 - false", () => { 51 | const result = expr.compareValue(5, 4); 52 | expect(result).toBeTruthy(); 53 | }); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/cypher/strategy/strategy.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CypherStrategy, 3 | // CypherStrategy, 4 | } from "../../../src"; 5 | 6 | const context = describe; 7 | 8 | describe("CypherStrategy", () => {}); 9 | --------------------------------------------------------------------------------