From 906c5405f7c29d3ea0fef91dab68f7178ac19efa Mon Sep 17 00:00:00 2001
From: Leonardo Christino <leomilho@gmail.com>
Date: Wed, 19 Jul 2023 23:00:13 +0200
Subject: [PATCH] feat(query): connect to new query format

---
 apps/web/.env.development                     |   2 +-
 apps/web/.env.production                      |   2 +-
 apps/web/.eslintignore                        |   7 +-
 apps/web/node.d.ts                            |   3 +
 apps/web/src/app/app.tsx                      |   4 +-
 .../navbar/AddDatabaseForm/index.tsx          |  33 ++-
 libs/shared/.eslintignore                     |   7 +-
 libs/shared/lib/data-access/api/database.ts   |   2 +
 libs/shared/lib/data-access/api/query.ts      |   3 +-
 .../store/graphQueryResultSlice.ts            |  18 +-
 .../data-access/store/querybuilderSlice.ts    |   3 +-
 .../querybuilder/model/BackendQueryFormat.tsx | 187 ++++++++++------
 .../querybuilder/model/graphology/model.ts    |   6 +
 .../querybuilder/model/graphology/utils.ts    |  56 ++++-
 libs/shared/lib/querybuilder/model/index.ts   |  13 +-
 .../querybuilder/model/reactflow/handles.tsx  |   7 +-
 .../lib/querybuilder/panel/querybuilder.tsx   |  34 ++-
 .../panel/shemaquerybuilder.stories.tsx       |  12 +-
 ...erybuilder-simple-disconnected.stories.tsx |  59 +++--
 .../stories/querybuilder-simple.stories.tsx   |  67 +++---
 .../querybuilder-single-entity.stories.tsx    |   9 +-
 ...erybuilder-single-relationship.stories.tsx |  20 +-
 .../pills/customFlowLines/connection.tsx      |  16 +-
 .../pills/customFlowPills/edge-line.tsx       |   2 +-
 .../entitypill/entitypill-full.stories.tsx    |   3 -
 .../customFlowPills/entitypill/entitypill.tsx |  12 +-
 .../relation-full_reactflow.stories.tsx       |   2 -
 .../relationpill/relationpill.tsx             |  10 +-
 .../query-utils/query-utils.spec.ts           |  16 ++
 .../querybuilder/query-utils/query-utils.ts   | 210 +++++++++++-------
 libs/shared/lib/schema/index.ts               |   1 -
 libs/shared/lib/schema/panel/schema.tsx       |  10 +
 libs/shared/lib/vis/nodelink/nodelinkvis.tsx  |   1 -
 libs/shared/node.d.ts                         |   1 +
 libs/shared/tsconfig.json                     |  68 +++---
 package.json                                  |   3 +-
 pnpm-lock.yaml                                |  75 +++----
 37 files changed, 580 insertions(+), 404 deletions(-)
 create mode 100644 libs/shared/lib/querybuilder/query-utils/query-utils.spec.ts

diff --git a/apps/web/.env.development b/apps/web/.env.development
index f6b68d9b0..ddbadbf75 100644
--- a/apps/web/.env.development
+++ b/apps/web/.env.development
@@ -3,5 +3,5 @@ VITE_BACKEND_WSS_URL=ws://localhost:3001/
 VITE_STAGING=dev
 VITE_SKIP_LOGIN=true
 VITE_BACKEND_USER=:3000
-VITE_BACKEND_QUERY=:8080
+VITE_BACKEND_QUERY=:3003
 VITE_BACKEND_SCHEMA=:3002
\ No newline at end of file
diff --git a/apps/web/.env.production b/apps/web/.env.production
index cf5c3e8e8..4c548bc85 100644
--- a/apps/web/.env.production
+++ b/apps/web/.env.production
@@ -3,5 +3,5 @@ VITE_BACKEND_WSS_URL=ws://api.graphpolaris.com/socket/
 VITE_STAGING=prod
 VITE_SKIP_LOGIN=false
 VITE_BACKEND_USER=/user
-VITE_BACKEND_QUERY=:8080
+VITE_BACKEND_QUERY=/query
 VITE_BACKEND_SCHEMA=/schema
\ No newline at end of file
diff --git a/apps/web/.eslintignore b/apps/web/.eslintignore
index 3421a7d28..ebd538ec9 100644
--- a/apps/web/.eslintignore
+++ b/apps/web/.eslintignore
@@ -1,3 +1,4 @@
-node_modules/*
-node_modules/
-node_modules
+node_modules/*
+node_modules/
+node_modules
+*.d.ts
\ No newline at end of file
diff --git a/apps/web/node.d.ts b/apps/web/node.d.ts
index 34f3d984d..3d2ced5bc 100644
--- a/apps/web/node.d.ts
+++ b/apps/web/node.d.ts
@@ -2,6 +2,9 @@ interface ImportMeta {
   env: {
     VITE_BACKEND_URL: string;
     VITE_BACKEND_WSS_URL: string;
+    VITE_BACKEND_USER: string;
+    VITE_BACKEND_SCHEMA: string;
+    VITE_BACKEND_QUERY: string;
     VITE_STAGING: string;
     VITE_KEYCLOAK_URL: string;
     VITE_KEYCLOAK_REALM: string;
diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx
index 97e18077a..9a83b2497 100644
--- a/apps/web/src/app/app.tsx
+++ b/apps/web/src/app/app.tsx
@@ -85,7 +85,9 @@ export function App(props: App) {
 
       if (query.edges.length === 0) {
         dispatch(resetGraphQueryResults());
-      } else api_query.execute(Query2BackendQuery(session.currentDatabase, query));
+      } else {
+        api_query.execute(Query2BackendQuery(session.currentDatabase, query));
+      }
     }
   }, [queryHash]);
 
diff --git a/apps/web/src/components/navbar/AddDatabaseForm/index.tsx b/apps/web/src/components/navbar/AddDatabaseForm/index.tsx
index 15d8fe443..8df366cd4 100644
--- a/apps/web/src/components/navbar/AddDatabaseForm/index.tsx
+++ b/apps/web/src/components/navbar/AddDatabaseForm/index.tsx
@@ -11,7 +11,7 @@
 import React, { useState, useEffect } from 'react';
 import { TextField, Button, NativeSelect } from '@mui/material';
 import styles from './add-database-form.module.scss';
-import { AddDatabaseRequest, DatabaseType, databaseNameMapping } from '@graphpolaris/shared/lib/data-access';
+import { AddDatabaseRequest, DatabaseType, databaseNameMapping, databaseProtocolMapping } from '@graphpolaris/shared/lib/data-access';
 
 /** AddDatabaseFormProps is an interface containing the AuthViewModel. */
 export interface AddDatabaseFormProps {
@@ -44,11 +44,20 @@ export default function AddDatabaseForm(props: AddDatabaseFormProps) {
     // internal_database_name: 'TweedeKamer',
     // type: DatabaseType.ArangoDB,
 
+    // username: 'neo4j',
+    // password: 'oL3nNlebrx4le2A0zxaFVqAo3HAvodHxwEiI_7_2JxI',
+    // url: '635176c8.databases.neo4j.io',
+    // port: 7687,
+    // name: 'neo4j',
+    // internal_database_name: 'neo4j',
+    // type: DatabaseType.Neo4j,
+
     username: 'neo4j',
-    password: 'oL3nNlebrx4le2A0zxaFVqAo3HAvodHxwEiI_7_2JxI',
-    url: '635176c8.databases.neo4j.io',
+    password: 'StrongPass2022',
+    url: 'localhost',
     port: 7687,
     name: 'neo4j',
+    protocol: 'neo4j://',
     internal_database_name: 'neo4j',
     type: DatabaseType.Neo4j,
   });
@@ -139,6 +148,24 @@ export default function AddDatabaseForm(props: AddDatabaseFormProps) {
               ))}
             </NativeSelect>
           </div>
+          <div className={styles.loginContainer}>
+            <NativeSelect
+              className={styles.passLabel}
+              value={state.protocol}
+              onChange={(event) => {
+                setState({
+                  ...state,
+                  protocol: event.currentTarget.value,
+                });
+              }}
+            >
+              {databaseProtocolMapping.map((protocol) => (
+                <option value={protocol} key={protocol}>
+                  {protocol}
+                </option>
+              ))}
+            </NativeSelect>
+          </div>
           <div className={styles.loginContainerRow}>
             <TextField
               className={styles.hostLabel}
diff --git a/libs/shared/.eslintignore b/libs/shared/.eslintignore
index 3421a7d28..86439d7da 100644
--- a/libs/shared/.eslintignore
+++ b/libs/shared/.eslintignore
@@ -1,3 +1,4 @@
-node_modules/*
-node_modules/
-node_modules
+node_modules/*
+node_modules/
+node_modules
+*.d.ts
diff --git a/libs/shared/lib/data-access/api/database.ts b/libs/shared/lib/data-access/api/database.ts
index bb7390c02..9c3abbe85 100644
--- a/libs/shared/lib/data-access/api/database.ts
+++ b/libs/shared/lib/data-access/api/database.ts
@@ -9,12 +9,14 @@ export enum DatabaseType {
 }
 
 export const databaseNameMapping: string[] = ['arangodb', 'neo4j'];
+export const databaseProtocolMapping: string[] = ['neo4j://', 'neo4j+s://'];
 
 export type AddDatabaseRequest = {
   name: string;
   internal_database_name: string;
   url: string;
   port: number;
+  protocol: string;
   username: string;
   password: string;
   type: DatabaseType; // Database type. 0 = ArangoDB, 1 = Neo4j
diff --git a/libs/shared/lib/data-access/api/query.ts b/libs/shared/lib/data-access/api/query.ts
index 8f5c9da66..5fd651fac 100644
--- a/libs/shared/lib/data-access/api/query.ts
+++ b/libs/shared/lib/data-access/api/query.ts
@@ -7,9 +7,10 @@ export const useQueryAPI = () => {
   const cache = useSessionCache();
   const { accessToken } = useAuthorizationCache();
   const domain = import.meta.env.VITE_BACKEND_URL;
+  const query_url = import.meta.env.VITE_BACKEND_QUERY;
 
   async function execute(query: BackendQueryFormat) {
-    const response = await fetch(`${domain}/query/execute/`, {
+    const response = await fetch(`${domain}${query_url}/execute/`, {
       method: 'POST',
       credentials: 'same-origin',
       headers: new Headers({
diff --git a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
index e6480df6b..1f8e0562f 100644
--- a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
+++ b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
@@ -1,6 +1,11 @@
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 import type { RootState } from './store';
 
+export interface GraphQueryResultFromBackendPayload {
+  payload: GraphQueryResultFromBackend;
+  type: string;
+}
+
 export interface GraphQueryResultFromBackend {
   nodes: {
     id: string;
@@ -58,13 +63,16 @@ export const graphQueryResultSlice = createSlice({
   // `createSlice` will infer the state type from the `initialState` argument
   initialState,
   reducers: {
-    assignNewGraphQueryResult: (state, action: PayloadAction<GraphQueryResultFromBackend>) => {
+    assignNewGraphQueryResult: (state, action: PayloadAction<GraphQueryResultFromBackendPayload>) => {
+      const payload = action.payload.payload;
+      console.log('!!!assignNewGraphQueryResult', action.payload.payload);
+
       // Maybe do some data quality checking and parsing
       // ...
 
       // Collect all the different nodetypes in the result
       const nodeTypes: string[] = [];
-      action.payload.nodes = action.payload.nodes.map((node) => {
+      payload.nodes = payload.nodes.map((node) => {
         // TODO FIXME!! Note: works only for arangodb
         let nodeType = node.id.split('/')[0];
         if (node.attributes?.labels?.length > 0) {
@@ -75,7 +83,7 @@ export const graphQueryResultSlice = createSlice({
         return node;
       });
 
-      action.payload.edges = action.payload.edges.map((edge) => {
+      payload.edges = payload.edges.map((edge) => {
         let edgeType = edge.id.split('/')[0];
         if (!edge.id.includes('/')) {
           edgeType = edge.attributes.Type as string;
@@ -85,8 +93,8 @@ export const graphQueryResultSlice = createSlice({
       });
 
       // Assign new state
-      state.nodes = action.payload.nodes as Node[];
-      state.edges = action.payload.edges;
+      state.nodes = payload.nodes as Node[];
+      state.edges = payload.edges;
       state.nodeTypes = nodeTypes;
     },
     resetGraphQueryResults: (state) => {
diff --git a/libs/shared/lib/data-access/store/querybuilderSlice.ts b/libs/shared/lib/data-access/store/querybuilderSlice.ts
index ad4501d09..724dafd97 100644
--- a/libs/shared/lib/data-access/store/querybuilderSlice.ts
+++ b/libs/shared/lib/data-access/store/querybuilderSlice.ts
@@ -2,8 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 import type { RootState } from './store';
 import { MultiGraph } from 'graphology';
 import { Attributes, SerializedGraph } from 'graphology-types';
-import { QueryMultiGraphology, QueryMultiGraph } from '@graphpolaris/shared/lib/querybuilder/model/graphology/utils';
-import { json } from 'd3';
+import { QueryMultiGraphology } from '../../querybuilder';
 
 // Define the initial state using that type
 export const initialState = {
diff --git a/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx b/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx
index a4c82df55..98608922b 100644
--- a/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx
+++ b/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx
@@ -16,8 +16,8 @@ export interface BackendQueryResultFormat {
   };
   entities: Entity[];
   relations: Relation[];
-  groupBys: GroupBy[];
-  machineLearning: MachineLearning[];
+  // groupBys: GroupBy[];
+  // machineLearning: MachineLearning[];
   limit: number;
   // modifiers: ModifierStruct[];
   // prefix: string;
@@ -26,20 +26,65 @@ export interface BackendQueryResultFormat {
 /** JSON query format used to send a query to the backend. */
 export interface BackendQueryFormat {
   databaseName: string;
-  return: {
-    entities: number[];
-    relations: number[];
-    groupBys: number[];
-  };
-  entities: Entity[];
-  relations: Relation[];
-  groupBys: GroupBy[];
-  machineLearning: MachineLearning[];
   limit: number;
+  return: string[];
+  query: QueryStruct[];
+  // entities: Entity[];
+  // relations: Relation[];
+  // groupBys: GroupBy[];
+  // machineLearning: MachineLearning[];
   // modifiers: ModifierStruct[];
   // prefix: string;
 }
 
+/** Interface for an entity in the JSON for the query. */
+export interface QueryStruct {
+  ID: string;
+  node: NodeStruct;
+}
+
+export interface NodeStruct {
+  label?: string;
+  ID?: string;
+  logic?: LogicStruct[];
+  relation?: RelationStruct;
+  subQuery?: QueryStruct;
+  export?: ExportNodeStruct[];
+}
+
+export interface ExportNodeStruct {
+  ID: number;
+  attribute: string;
+}
+
+export interface LogicStruct {
+  ID: number;
+  attribute: string;
+  operation: LogicOperationType;
+}
+
+export interface FilterStruct {
+  ID: number;
+  attribute: string;
+  operation: FilterOperationType;
+}
+
+export interface RelationStruct {
+  ID?: string;
+  label?: string;
+  depth?: QuerySearchDepthStruct;
+  direction: 'TO' | 'FROM';
+  node?: NodeStruct;
+}
+
+export interface QuerySearchDepthStruct {
+  min: number;
+  max: number;
+}
+
+export type FilterOperationType = 'AND' | 'OR';
+export type LogicOperationType = 'AND' | 'OR';
+
 /** Interface for an entity in the JSON for the query. */
 export interface Entity {
   name: string;
@@ -59,21 +104,33 @@ export interface Relation {
   constraints: Constraint[];
 }
 
-/** JSON query format used to send a query to the backend. */
-export interface TranslatedJSONQuery {
-  return: {
-    entities: number[];
-    relations: number[];
-    groupBys: number[];
-  };
-  entities: Entity[];
-  relations: Relation[];
-  groupBys: GroupBy[];
-  machineLearning: MachineLearning[];
-  limit: number;
-}
-
-////////////////////
+// /** Interface for an relation in the JSON for the query. */
+// export interface Relation {
+//   name: string;
+//   ID: number;
+//   fromType: string;
+//   fromID: number;
+//   toType: string;
+//   toID: number;
+//   depth: { min: number; max: number };
+//   constraints: Constraint[];
+// }
+
+// /** JSON query format used to send a query to the backend. */
+// export interface TranslatedJSONQuery {
+//   return: {
+//     entities: number[];
+//     relations: number[];
+//     groupBys: number[];
+//   };
+//   entities: Entity[];
+//   relations: Relation[];
+//   groupBys: GroupBy[];
+//   machineLearning: MachineLearning[];
+//   limit: number;
+// }
+
+// ////////////////////
 
 /**
  * Constraint datatypes created from the attributes of a relation or entity.
@@ -90,42 +147,42 @@ export interface Constraint {
   value: string;
 }
 
-/** Interface for a function in the JSON for the query. */
-export interface GroupBy {
-  ID: number;
-
-  groupType: string;
-  groupID: number[];
-  groupAttribute: string;
-
-  byType: string;
-  byID: number[];
-  byAttribute: string;
-
-  appliedModifier: string;
-  relationID: number;
-  constraints: Constraint[];
-}
-
-/** Interface for Machine Learning algorithm */
-export interface MachineLearning {
-  ID?: number;
-  queuename: string;
-  parameters: string[];
-}
-
-/** Interface for what the JSON needs for link predicition */
-export interface LinkPrediction {
-  queuename: string;
-  parameters: {
-    key: string;
-    value: string;
-  }[];
-}
-
-export interface ModifierStruct {
-  type: string;
-  selectedType: string;
-  selectedTypeID: number;
-  attributeIndex: number;
-}
+// /** Interface for a function in the JSON for the query. */
+// export interface GroupBy {
+//   ID: number;
+
+//   groupType: string;
+//   groupID: number[];
+//   groupAttribute: string;
+
+//   byType: string;
+//   byID: number[];
+//   byAttribute: string;
+
+//   appliedModifier: string;
+//   relationID: number;
+//   constraints: Constraint[];
+// }
+
+// /** Interface for Machine Learning algorithm */
+// export interface MachineLearning {
+//   ID?: number;
+//   queuename: string;
+//   parameters: string[];
+// }
+
+// /** Interface for what the JSON needs for link predicition */
+// export interface LinkPrediction {
+//   queuename: string;
+//   parameters: {
+//     key: string;
+//     value: string;
+//   }[];
+// }
+
+// export interface ModifierStruct {
+//   type: string;
+//   selectedType: string;
+//   selectedTypeID: number;
+//   attributeIndex: number;
+// }
diff --git a/libs/shared/lib/querybuilder/model/graphology/model.ts b/libs/shared/lib/querybuilder/model/graphology/model.ts
index 3d3060fa4..6fe37af6f 100644
--- a/libs/shared/lib/querybuilder/model/graphology/model.ts
+++ b/libs/shared/lib/querybuilder/model/graphology/model.ts
@@ -9,6 +9,7 @@ import { GeneralDescription, InputNodeType } from '../logic/general';
 // }
 
 export interface NodeAttribute extends SchemaAttribute {
+  handleId: string;
   // nodeCount: number;
   // summedNullAmount: number;
   // connectedRatio: number;
@@ -16,6 +17,7 @@ export interface NodeAttribute extends SchemaAttribute {
 }
 
 export type NodeDefaults = {
+  id?: string;
   type: string;
   width?: number;
   height?: number;
@@ -25,6 +27,8 @@ export type NodeDefaults = {
 /** Interface for the data in an entity node. */
 export interface EntityData {
   name: string;
+  leftRelationHandleId?: string;
+  rightRelationHandleId?: string;
 }
 
 /** Interface for the data in an relation node. */
@@ -32,6 +36,8 @@ export interface RelationData {
   name: string;
   collection: string;
   depth: { min: number; max: number };
+  leftEntityHandleId?: string;
+  rightEntityHandleId?: string;
 }
 
 export interface LogicData {
diff --git a/libs/shared/lib/querybuilder/model/graphology/utils.ts b/libs/shared/lib/querybuilder/model/graphology/utils.ts
index a3d49d2b4..027d1661b 100644
--- a/libs/shared/lib/querybuilder/model/graphology/utils.ts
+++ b/libs/shared/lib/querybuilder/model/graphology/utils.ts
@@ -1,9 +1,10 @@
 import { setQuerybuilderNodes, store } from '@graphpolaris/shared/lib/data-access/store';
 import Graph, { MultiGraph } from 'graphology';
 import { Attributes as GAttributes, Attributes, SerializedGraph } from 'graphology-types';
-import { QueryGraphNodes } from './model';
+import { EntityNodeAttributes, LogicNodeAttributes, QueryGraphNodes, RelationNodeAttributes } from './model';
 import { XYPosition } from 'reactflow';
-import { NodeAttribute, SchemaGraphology } from '@graphpolaris/shared/lib/schema';
+import { Handles, QueryElementTypes } from '../reactflow';
+import { getHandleId } from '..';
 
 /** monospace fontsize table */
 const widthPerFontsize = {
@@ -15,13 +16,10 @@ const widthPerFontsize = {
 export type QueryMultiGraph = SerializedGraph<QueryGraphNodes, GAttributes, GAttributes>;
 
 export class QueryMultiGraphology extends MultiGraph<QueryGraphNodes, GAttributes, GAttributes> {
-  public addPill2Graphology(
-    attributes: QueryGraphNodes,
-    id: string = (Date.now() + Math.floor(Math.random() * 1000)).toString()
-  ): string | null {
-    const { type, name, handles } = attributes;
-    if (!type || !name) return null;
-    if (!handles) attributes.handles = [];
+  public addPill2Graphology(attributes: QueryGraphNodes): QueryGraphNodes {
+    const { type, name } = attributes;
+    if (!type || !name) throw Error('type or name is not defined');
+    if (!attributes.id) attributes.id = 'id_' + (Date.now() + Math.floor(Math.random() * 1000)).toString();
     let { x, y } = attributes;
 
     // Check if x and y are present, otherwise set them to 0
@@ -31,13 +29,49 @@ export class QueryMultiGraphology extends MultiGraph<QueryGraphNodes, GAttribute
     // Get the width and height of a node
     const { width, height } = calcWidthHeightOfPill(attributes);
 
+    // fix handles
+    if (type === QueryElementTypes.Entity) {
+      (attributes as EntityNodeAttributes).leftRelationHandleId = getHandleId(attributes.id, name, type, Handles.EntityLeft, '');
+      (attributes as EntityNodeAttributes).rightRelationHandleId = getHandleId(attributes.id, name, type, Handles.EntityRight, '');
+    } else if (type === QueryElementTypes.Relation) {
+      (attributes as RelationNodeAttributes).leftEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationLeft, '');
+      (attributes as RelationNodeAttributes).rightEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationRight, '');
+    } else if (type === QueryElementTypes.Logic) {
+    }
+
     // Add a node to the graphology object
-    const nodeId = this.addNode(id, { ...attributes, x, y, width, height });
+    const nodeId = this.addNode(attributes.id, { ...attributes, x, y, width, height });
+
+    // Set the new nodes in the query builder slice TODO: maybe remove for efficiency
+    store.dispatch(setQuerybuilderNodes(this.export()));
+
+    return attributes;
+  }
+
+  public addEdge2Graphology(source: QueryGraphNodes, target: QueryGraphNodes, attributes: GAttributes): string | null {
+    // fix handles
+    attributes.sourceHandle = getHandleId(
+      source.id + '_TO_' + target.id,
+      source.name,
+      source.type,
+      source.type === QueryElementTypes.Entity ? Handles.EntityRight : Handles.RelationRight,
+      ''
+    );
+    attributes.targetHandle = getHandleId(
+      source.id + '_TO_' + target.id,
+      target.name,
+      target.type,
+      target.type === QueryElementTypes.Entity ? Handles.EntityLeft : Handles.RelationLeft,
+      ''
+    );
+
+    // Add an edge to the graphology object
+    const edgeId = this.addEdge(source.id, target.id, attributes);
 
     // Set the new nodes in the query builder slice TODO: maybe remove for efficiency
     store.dispatch(setQuerybuilderNodes(this.export()));
 
-    return nodeId;
+    return edgeId;
   }
 }
 
diff --git a/libs/shared/lib/querybuilder/model/index.ts b/libs/shared/lib/querybuilder/model/index.ts
index 835ff79fd..d9af8a031 100644
--- a/libs/shared/lib/querybuilder/model/index.ts
+++ b/libs/shared/lib/querybuilder/model/index.ts
@@ -8,6 +8,7 @@ export * from './reactflow';
 
 type ExtraProps = { extra?: string; separator?: string };
 export function getHandleId(
+  nodeId: string,
   nodeName: string,
   nodeType: string,
   attributeName: string,
@@ -16,18 +17,18 @@ export function getHandleId(
 ): string {
   if (!extra) extra = '';
   if (!separator) separator = '__';
-  return [nodeType, nodeName, attributeName, attributeType, extra].join(separator);
+  return [nodeId, nodeType, nodeName, attributeName, attributeType, extra].join(separator);
 }
 export function getHandleIdFromGraphology(node: QueryGraphNodes, attribute: NodeAttribute, options: ExtraProps = {}): string {
-  return getHandleId(node.name, node.type, attribute.name, attribute.type, options);
+  return getHandleId(node.id || '', node.name, node.type, attribute.name, attribute.type, options);
 }
 export function getHandleIdFromReactflow(node: SchemaReactflowNode, attribute: NodeAttribute, options: ExtraProps = {}): string {
-  return getHandleId(node.data.name, node.type, attribute.name, attribute.type, options);
+  return getHandleId(node.id, node.data.name, node.type, attribute.name, attribute.type, options);
 }
 export function fromHandleId(
   handleId: string,
   separator: string = '__'
-): { nodeName: string; nodeType: string; attributeName: string; attributeType: string } {
-  const [nodeType, nodeName, attributeName, attributeType] = handleId.split(separator);
-  return { nodeType, nodeName, attributeName, attributeType };
+): { nodeId: string; nodeName: string; nodeType: string; attributeName: string; attributeType: string } {
+  const [nodeId, nodeType, nodeName, attributeName, attributeType] = handleId.split(separator);
+  return { nodeId, nodeType, nodeName, attributeName, attributeType };
 }
diff --git a/libs/shared/lib/querybuilder/model/reactflow/handles.tsx b/libs/shared/lib/querybuilder/model/reactflow/handles.tsx
index d37ea317e..cb55e4d7d 100644
--- a/libs/shared/lib/querybuilder/model/reactflow/handles.tsx
+++ b/libs/shared/lib/querybuilder/model/reactflow/handles.tsx
@@ -13,10 +13,11 @@ import { SchemaReactflowNode, QueryElementTypes } from './model';
 
 /** Links need handles to what they are connected to (and which side) */
 export enum Handles {
-  RelationLeft = 'leftEntityHandle', //target
-  RelationRight = 'rightEntityHandle', //target
+  RelationLeft = 'relationLeftHandle', //target
+  RelationRight = 'relationRightHandle', //source
   ToAttribute = 'attributesHandle', //target
-  ToRelation = 'relationsHandle', //source
+  EntityLeft = 'entityLeftHandle', //source
+  EntityRight = 'entityRightHandle', //target
   OnAttribute = 'onAttributeHandle', //source
   ReceiveFunction = 'receiveFunctionHandle', //target
   FunctionBase = 'functionHandle_', // + name from FunctionTypes args //source
diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
index 98d6cf335..ececf9132 100644
--- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx
+++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
@@ -217,10 +217,13 @@ export const QueryBuilderInner: React.FC = () => {
     if (data.length == 0 || !reactFlow) return;
 
     const dragData = JSON.parse(data);
+    console.log(reactFlowWrapper);
+
+    const bounds = reactFlowWrapper?.current?.getBoundingClientRect() || { x: 0, y: 0 };
     const position = reactFlow.project({
       //TODO: this position should be centre of entity, rather than topleft
-      x: event.clientX,
-      y: event.clientY,
+      x: event.clientX - bounds.x,
+      y: event.clientY - bounds.y,
     });
 
     switch (dragData.type) {
@@ -231,38 +234,35 @@ export const QueryBuilderInner: React.FC = () => {
           y: position.y,
           name: dragData.name,
         });
+        dispatch(setQuerybuilderNodes(graphologyGraph.export()));
         break;
       // Creates a relation element and will also create the 2 related entities together with the connections
       case QueryElementTypes.Relation:
-        const relationId = graphologyGraph.addPill2Graphology({
+        const relation = graphologyGraph.addPill2Graphology({
           type: QueryElementTypes.Relation,
           x: position.x,
           y: position.y,
           depth: { min: 0, max: 1 },
           // name: dragData.name,
-          name: dragData.collection, // TODO leave collection or use name?
+          name: dragData.collection,
           collection: dragData.collection,
         });
-        const leftEntityId = graphologyGraph.addPill2Graphology({
+        const leftEntity = graphologyGraph.addPill2Graphology({
           type: QueryElementTypes.Entity,
           ...RelationPosToFromEntityPos(position),
           name: dragData.from,
         });
-        const rightEntityId = graphologyGraph.addPill2Graphology({
+        const rightEntity = graphologyGraph.addPill2Graphology({
           type: QueryElementTypes.Entity,
           ...RelationPosToToEntityPos(position),
           name: dragData.to,
         });
 
-        graphologyGraph.addEdge(leftEntityId, relationId, {
+        graphologyGraph.addEdge2Graphology(leftEntity, relation, {
           type: 'connection',
-          sourceHandle: Handles.ToRelation,
-          targetHandle: Handles.RelationLeft,
         });
-        graphologyGraph.addEdge(rightEntityId, relationId, {
+        graphologyGraph.addEdge2Graphology(relation, rightEntity, {
           type: 'connection',
-          sourceHandle: Handles.ToRelation,
-          targetHandle: Handles.RelationRight,
         });
 
         if (config.autoSendQueries) {
@@ -361,17 +361,15 @@ export const QueryBuilderInner: React.FC = () => {
     if (!firstLeftLogicInput) return;
 
     // logicAttributes[0].handles = [connectingNodeId.current.handleId];
-    const logicNode = {
+    const logicNode = graphologyGraph.addPill2Graphology({
       name: value.name,
       type: QueryElementTypes.Logic,
       x: position.x,
       y: position.y,
       logic: logic,
-    };
-    const logicId = graphologyGraph.addPill2Graphology(logicNode);
-    if (!logicId) return;
+    });
 
-    graphologyGraph.addEdge(params.nodeId, logicId, {
+    graphologyGraph.addEdge(params.nodeId, logicNode.id, {
       type: 'connection',
       sourceHandle: params.handleId, // newAttribute data?
       targetHandle: getHandleId(logicNode.name, logicNode.type, firstLeftLogicInput.name, firstLeftLogicInput.type.join(''), {
@@ -616,7 +614,7 @@ export const QueryBuilder = () => {
         gap: '1rem',
       }}
     >
-      <QueryBuilderPills />
+      {/* <QueryBuilderPills /> */}
       <ReactFlowProvider>
         <QueryBuilderInner />
       </ReactFlowProvider>
diff --git a/libs/shared/lib/querybuilder/panel/shemaquerybuilder.stories.tsx b/libs/shared/lib/querybuilder/panel/shemaquerybuilder.stories.tsx
index 6470560bd..f0ee5ed0d 100644
--- a/libs/shared/lib/querybuilder/panel/shemaquerybuilder.stories.tsx
+++ b/libs/shared/lib/querybuilder/panel/shemaquerybuilder.stories.tsx
@@ -1,22 +1,12 @@
 import React from 'react';
 import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
-import {
-  colorPaletteConfigSlice,
-  graphQueryResultSlice,
-  querybuilderSlice,
-  schemaSlice,
-  setQuerybuilderNodes,
-  setSchema,
-  store,
-} from '@graphpolaris/shared/lib/data-access/store';
+import { setQuerybuilderNodes, setSchema, store } from '@graphpolaris/shared/lib/data-access/store';
 import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme';
 import { SchemaUtils } from '@graphpolaris/shared/lib/schema/schema-utils';
 import { Schema } from '@graphpolaris/shared/lib/schema/panel';
 import { movieSchemaRaw } from '@graphpolaris/shared/lib/mock-data';
 import { QueryBuilder } from '@graphpolaris/shared/lib/querybuilder';
-import { configureStore } from '@reduxjs/toolkit';
-import { configSlice } from '@graphpolaris/shared/lib/data-access/store/configSlice';
 import { QueryMultiGraphology } from '@graphpolaris/shared/lib/querybuilder/model/graphology/utils';
 
 const SchemaAndQueryBuilder = () => {
diff --git a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx
index d69660dd4..c5498bae2 100644
--- a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx
+++ b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx
@@ -11,7 +11,7 @@ import { configureStore } from '@reduxjs/toolkit';
 import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import QueryBuilderInner from '../querybuilder';
-import { Handles, NodeAttribute, QueryMultiGraphology } from '../../model';
+import { Handles, NodeAttribute, QueryElementTypes, QueryMultiGraphology, getHandleId } from '../../model';
 import { SchemaUtils } from '../../../schema/schema-utils';
 import { ReactFlowProvider } from 'reactflow';
 
@@ -53,39 +53,34 @@ export const SimpleDisconnected = {
 
       store.dispatch(setSchema(schema.export()));
 
-      graph.addPill2Graphology(
-        {
-          type: 'entity',
-          x: 100,
-          y: 100,
-          name: 'Entity Pill',
-          attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
-        },
-        '0'
-      );
-      graph.addPill2Graphology(
-        {
-          type: 'entity',
-          x: 200,
-          y: 200,
-          name: 'Entity Pill 2',
-          attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
-        },
-        '10'
-      );
+      const entity1 = graph.addPill2Graphology({
+        id: '0',
+        type: 'entity',
+        x: 100,
+        y: 100,
+        name: 'Airport 1',
+        attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
+      });
+      const entity2 = graph.addPill2Graphology({
+        id: '10',
+        type: 'entity',
+        x: 200,
+        y: 200,
+        name: 'Airport 2',
+        attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
+      });
 
       // graph.addNode('0', { type: 'entity', x: 100, y: 100, name: 'Entity Pill' });
-      graph.addPill2Graphology(
-        {
-          type: 'relation',
-          x: 140,
-          y: 140,
-          name: 'Relation Pill',
-          depth: { min: 0, max: 1 },
-          attributes: schema.getEdgeAttribute('entity:entity_entityentity', 'attributes'),
-        },
-        '1'
-      );
+      const relation1 = graph.addPill2Graphology({
+        id: '1',
+        type: 'relation',
+        x: 140,
+        y: 140,
+        name: 'Flight between airports',
+        collection: 'Relation Pill',
+        depth: { min: 0, max: 1 },
+        attributes: schema.getEdgeAttribute('entity:entity_entityentity', 'attributes'),
+      });
       store.dispatch(setQuerybuilderNodes(graph.export()));
 
       return (
diff --git a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx
index 987c59408..955a41623 100644
--- a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx
+++ b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx
@@ -11,7 +11,7 @@ import { configureStore } from '@reduxjs/toolkit';
 import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import QueryBuilder from '../querybuilder';
-import { Handles, NodeAttribute, QueryMultiGraphology } from '../../model';
+import { Handles, NodeAttribute, QueryElementTypes, QueryMultiGraphology, getHandleId } from '../../model';
 import { SchemaUtils } from '../../../schema/schema-utils';
 
 const Component: Meta<typeof QueryBuilder> = {
@@ -67,39 +67,34 @@ export const Simple = {
 
     store.dispatch(setSchema(schema.export()));
 
-    graph.addPill2Graphology(
-      {
-        type: 'entity',
-        x: 100,
-        y: 100,
-        name: 'Airport 1',
-        attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
-      },
-      '0'
-    );
-    graph.addPill2Graphology(
-      {
-        type: 'entity',
-        x: 200,
-        y: 200,
-        name: 'Airport 2',
-        attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
-      },
-      '10'
-    );
+    const entity1 = graph.addPill2Graphology({
+      id: '0',
+      type: 'entity',
+      x: 100,
+      y: 100,
+      name: 'Airport 1',
+      attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
+    });
+    const entity2 = graph.addPill2Graphology({
+      id: '10',
+      type: 'entity',
+      x: 200,
+      y: 200,
+      name: 'Airport 2',
+      attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
+    });
 
     // graph.addNode('0', { type: 'entity', x: 100, y: 100, name: 'Entity Pill' });
-    graph.addPill2Graphology(
-      {
-        type: 'relation',
-        x: 140,
-        y: 140,
-        name: 'Flight between airports',
-        depth: { min: 0, max: 1 },
-        attributes: schema.getEdgeAttribute('entity:entity_entityentity', 'attributes'),
-      },
-      '1'
-    );
+    const relation1 = graph.addPill2Graphology({
+      id: '1',
+      type: 'relation',
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+      attributes: schema.getEdgeAttribute('entity:entity_entityentity', 'attributes'),
+    });
     // addPill2Graphology(
     //   '2',
     //   {
@@ -141,15 +136,11 @@ export const Simple = {
     //   },
     //   graph
     // );
-    graph.addEdge('0', '1', {
+    graph.addEdge2Graphology(entity1, relation1, {
       type: 'connection',
-      sourceHandle: Handles.ToRelation,
-      targetHandle: Handles.RelationLeft,
     });
-    graph.addEdge('10', '1', {
+    graph.addEdge2Graphology(relation1, entity2, {
       type: 'connection',
-      sourceHandle: Handles.ToRelation,
-      targetHandle: Handles.RelationRight,
     });
     // console.log(graph.getNodeAttributes('2'));
     // graph.addEdge('2', '1', { type: 'attribute_connection' });
diff --git a/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-entity.stories.tsx b/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-entity.stories.tsx
index 09397f5c3..a5ca1fc7f 100644
--- a/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-entity.stories.tsx
+++ b/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-entity.stories.tsx
@@ -11,7 +11,7 @@ import { configureStore } from '@reduxjs/toolkit';
 import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import QueryBuilder from '../querybuilder';
-import { QueryMultiGraphology } from '../../model';
+import { QueryElementTypes, QueryMultiGraphology, getHandleId } from '../../model';
 
 const Component: Meta<typeof QueryBuilder> = {
   component: QueryBuilder,
@@ -38,7 +38,12 @@ export const SingleEntity = {
   args: {},
   play: async () => {
     const graph = new QueryMultiGraphology();
-    graph.addPill2Graphology({ type: 'entity', x: 100, y: 100, name: 'Entity Pill' }, '0');
+    graph.addPill2Graphology({
+      type: 'entity',
+      x: 100,
+      y: 100,
+      name: 'Entity Pill',
+    });
     store.dispatch(setQuerybuilderNodes(graph.export()));
   },
 };
diff --git a/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-relationship.stories.tsx b/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-relationship.stories.tsx
index 26d064f9d..017a42f10 100644
--- a/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-relationship.stories.tsx
+++ b/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-relationship.stories.tsx
@@ -11,7 +11,7 @@ import { configureStore } from '@reduxjs/toolkit';
 import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import QueryBuilder from '../querybuilder';
-import { QueryMultiGraphology } from '../../model';
+import { QueryElementTypes, QueryMultiGraphology, getHandleId } from '../../model';
 
 const Component: Meta<typeof QueryBuilder> = {
   component: QueryBuilder,
@@ -38,16 +38,14 @@ export const SingleRelationship = {
   args: {},
   play: async () => {
     const graph = new QueryMultiGraphology();
-    graph.addPill2Graphology(
-      {
-        type: 'relation',
-        x: 140,
-        y: 140,
-        name: 'Relation Pill',
-        depth: { min: 0, max: 1 },
-      },
-      '0'
-    );
+    graph.addPill2Graphology({
+      type: 'relation',
+      x: 140,
+      y: 140,
+      name: 'Relation Pill',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
     store.dispatch(setQuerybuilderNodes(graph.export()));
   },
 };
diff --git a/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx b/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx
index 50625840f..ef15a7b00 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx
@@ -1,26 +1,12 @@
 import React from 'react';
 import { EdgeProps, getSmoothStepPath, Position } from 'reactflow';
-import { handles } from '../../graph/reactflow/pillHandles';
 
 /**
  * A custom query element edge line component.
  * @param {EdgeProps} param0 The coordinates for the start and end point, the id and the style.
  */
 //  export const EntityRFPill = React.memo(({ data }: { data: any }) => {
-export function ConnectionLine({
-  id,
-  sourceX,
-  sourceY,
-  targetX,
-  targetY,
-  style,
-  sourceHandleId,
-  targetHandleId,
-  source,
-  target,
-  sourcePosition,
-  targetPosition,
-}: EdgeProps) {
+export function ConnectionLine({ id, sourceX, sourceY, targetX, targetY, style, sourcePosition, targetPosition }: EdgeProps) {
   //Centering the line
   // sourceY -= 3;
   // targetY -= 3;
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/edge-line.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/edge-line.tsx
index 0b119180b..ffb927095 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/edge-line.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/edge-line.tsx
@@ -10,7 +10,7 @@
  * See testing plan for more details.*/
 import React from 'react';
 import { EdgeProps, getSmoothStepPath, Position } from 'reactflow';
-import { Handles } from '../../graph-reactflow/handles';
+import { Handles } from '../../model';
 
 /**
  * A custom query element edge line component.
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx
index 23990d1ff..2e408ee62 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx
@@ -4,10 +4,7 @@ import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/
 import { configureStore } from '@reduxjs/toolkit';
 import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
-import { MultiGraph } from 'graphology';
 import { QueryBuilder } from '../../../panel';
-import { QueryGraph } from '../../../model/graphology/model';
-import { circular } from 'graphology-layout';
 import { QueryMultiGraphology } from '@graphpolaris/shared/lib/querybuilder/model/graphology/utils';
 
 const Component: Meta<typeof QueryBuilder> = {
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
index fbac410a0..960834e2d 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
@@ -3,7 +3,7 @@ import { useTheme } from '@mui/material';
 import React, { MouseEventHandler, useEffect } from 'react';
 import { ReactFlow, Handle, Position, getConnectedEdges } from 'reactflow';
 import styles from './entitypill.module.scss';
-import { SchemaReactflowEntityNode, Handles, getHandleId, getHandleIdFromReactflow } from '../../../model';
+import { SchemaReactflowEntityNode, Handles, getHandleIdFromReactflow } from '../../../model';
 import { SchemaAttribute } from '@graphpolaris/shared/lib/schema';
 import { styleHandleMap } from '../../utils';
 import { useQuerybuilderGraph } from '@graphpolaris/shared/lib/data-access';
@@ -64,7 +64,15 @@ export const EntityFlowElement = React.memo((node: SchemaReactflowEntityNode) =>
       }
     >
       <Handle
-        id={getHandleId(data.name, data.type, Handles.ToRelation, '')}
+        // id={getHandleId(data.name, data.type, Handles.ToRelation, '')}
+        id={data.leftRelationHandleId}
+        type="target"
+        position={Position.Left}
+        className={styles.handle_to_relation}
+      />
+      <Handle
+        // id={getHandleId(data.name, data.type, Handles.ToRelation, '')}
+        id={data.rightRelationHandleId}
         type="source"
         position={Position.Right}
         className={styles.handle_to_relation}
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx
index ec6af4a20..d62df8343 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx
@@ -6,8 +6,6 @@ import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import { MultiGraph } from 'graphology';
 import { QueryBuilder } from '../../../panel';
-import { QueryGraph } from '../../../model/graphology/model';
-import { circular } from 'graphology-layout';
 import { QueryMultiGraphology } from '../../../model';
 
 const Component: Meta<typeof QueryBuilder> = {
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx
index d94494203..a714cd120 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx
@@ -82,13 +82,13 @@ export const RelationPill = memo((node: SchemaReactflowRelationNode) => {
           // style={{ transform: 'translate(-100px,0)' }}
         >
           <Handle
-            id={getHandleId(data.name, data.type, Handles.RelationLeft, '')}
+            id={data.leftEntityHandleId}
             type="target"
             position={Position.Left}
             className={styles.relationHandleLeft + ' ' + (false ? styles.handleConnectedBorderLeft : '')}
           />
         </span>
-        <span className={styles.relationHandleFiller}>
+        {/* <span className={styles.relationHandleFiller}>
           <Handle
             id={getHandleId(data.name, data.type, Handles.ToAttribute, '')}
             type="target"
@@ -103,7 +103,7 @@ export const RelationPill = memo((node: SchemaReactflowRelationNode) => {
             position={Position.Left}
             className={styles.relationHandleFunction + ' ' + (false ? styles.handleConnectedFill : '')}
           />
-        </span>
+        </span> */}
         <div className={styles.relationDataWrapper}>
           <span className={styles.relationSpan}>{data?.name}</span>
           <span className={styles.relationInputHolder}>
@@ -166,8 +166,8 @@ export const RelationPill = memo((node: SchemaReactflowRelationNode) => {
         </div>
         <span className={styles.relationHandleFiller}>
           <Handle
-            id={getHandleId(data.name, data.type, Handles.RelationRight, '')}
-            type="target"
+            id={data.rightEntityHandleId}
+            type="source"
             position={Position.Right}
             className={styles.relationHandleRight + ' ' + (false ? styles.handleConnectedBorderRight : '')}
           />
diff --git a/libs/shared/lib/querybuilder/query-utils/query-utils.spec.ts b/libs/shared/lib/querybuilder/query-utils/query-utils.spec.ts
new file mode 100644
index 000000000..8e9b2b1f2
--- /dev/null
+++ b/libs/shared/lib/querybuilder/query-utils/query-utils.spec.ts
@@ -0,0 +1,16 @@
+import { describe, it, expect } from 'vitest';
+// import { Query2BackendQuery } from './query-utils';
+import { QueryMultiGraphology } from '../model/graphology/utils';
+
+describe('QueryUtils', () => {
+  it('should create an instance', () => {
+    expect(true).toBeTruthy();
+  });
+
+  // it('should run query translation', () => {
+  // const graph = new QueryMultiGraphology(); // FIXME: not working for some reason
+
+  // let ret = Query2BackendQuery('database', graph.export());
+  // console.log(ret);
+  // });
+});
diff --git a/libs/shared/lib/querybuilder/query-utils/query-utils.ts b/libs/shared/lib/querybuilder/query-utils/query-utils.ts
index 7fc1cfe0c..71a935e62 100644
--- a/libs/shared/lib/querybuilder/query-utils/query-utils.ts
+++ b/libs/shared/lib/querybuilder/query-utils/query-utils.ts
@@ -1,90 +1,142 @@
-import { EntityNodeAttributes, RelationNodeAttributes } from '../model/graphology/model';
+import { EntityNodeAttributes, QueryGraphNodes, RelationNodeAttributes } from '../model/graphology/model';
 import { QueryMultiGraph } from '../model/graphology/utils';
-import { BackendQueryFormat } from '../model/BackendQueryFormat';
-import { Handles, QueryElementTypes } from '../model';
+import { BackendQueryFormat, NodeStruct, QueryStruct, RelationStruct } from '../model/BackendQueryFormat';
+import { Handles, QueryElementTypes, fromHandleId } from '../model';
+import { get } from 'http';
+import { SerializedEdge } from 'graphology-types';
+import { G } from 'vitest/dist/types-fafda418';
+
+// export type QueryI {
+
+// }
 
 /**
  * Converts the ReactFlow query to a json data structure to send the query to the backend.
  * @returns {BackendQueryFormat} A JSON object in the `JSONFormat`.
  */
 export function Query2BackendQuery(databaseName: string, graph: QueryMultiGraph): BackendQueryFormat {
-  // dict of nodes per type
-  const entities = graph.nodes
-    .filter((n) => n.attributes?.type === QueryElementTypes.Entity)
-    .map((n) => ({
-      name: n.attributes!.name,
-      ID: Number(n.key),
-      constraints: [],
-    }));
-
-  const relations = graph.nodes
-    .filter((n) => n.attributes?.type === QueryElementTypes.Relation)
-    .map((n) => {
-      const attributes = n.attributes as RelationNodeAttributes;
-      const leftEdge = graph.edges.filter((e) => e.target === n.key && e.attributes!.targetHandle === Handles.RelationLeft)?.[0];
-      const rightEdge = graph.edges.filter((e) => e.target === n.key && e.attributes!.targetHandle === Handles.RelationRight)?.[0];
-      if (!leftEdge || !rightEdge) throw Error('Malformed Graph! One or more edges of a relation node do not exist');
-
-      const leftType = graph.nodes.filter((n) => n.key === leftEdge.source)?.[0]?.attributes?.type;
-      const rightType = graph.nodes.filter((n) => n.key === rightEdge.source)?.[0]?.attributes?.type;
-      if (!rightType || !leftType) throw Error('Malformed Graph! edges must have a type');
-
-      return {
-        name: attributes!.collection,
-        ID: Number(n.key),
-        depth: attributes!.depth,
-        fromType: leftType,
-        fromID: Number(leftEdge.source), // need to treat function==groupby
-        toType: rightType,
-        toID: Number(rightEdge.source), // need to treat function==groupby
-        constraints: [],
-      };
-    });
-
-  // TODO: Groupby not implemented (constraints)
-  const result: BackendQueryFormat = {
+  let query: BackendQueryFormat = {
     databaseName: databaseName,
-    return: {
-      entities: entities.map((e) => e.ID),
-      relations: relations.map((r) => r.ID),
-      groupBys: [],
-    },
-    entities: entities,
-    relations: relations,
-    groupBys: [], // TODO
-    machineLearning: [], // TODO
+    query: [],
     limit: 5000, // TODO
+    return: ['*'], // TODO
   };
-  console.log(result, graph);
-
-  //note that attribute nodes are not processed here, but are detected if they are connected to any entity/relation/function node
-
-  //add nodes to JSON query
-  // entityNodes.forEach((node) => {
-  //     this.AddEntityToJsonObject(result, node);
-  // });
-  // relationNodes.forEach((node) => {
-  //     this.AddRelationToJsonObject(result, node);
-  // });
-
-  // functionNodes.forEach((functionNode: FunctionNode) => {
-  //   switch (functionNode.data?.functionType) {
-  //     case FunctionTypes.GroupBy:
-  //       this.AddGroupByToJsonObject(result, functionNode);
-  //       break;
-  //     case FunctionTypes.link:
-  //       this.AddLinkPredictionToJsonObject(result, functionNode);
-  //       break;
-  //     case FunctionTypes.communityDetection:
-  //       this.AddCommunityDetectionToJsonObject(result, functionNode);
-  //       break;
-  //     case FunctionTypes.centrality:
-  //       this.AddCentralityToJsonObject(result, functionNode);
-  //       break;
-  //     case FunctionTypes.shortestPath:
-  //       this.addShortestPathToJsonOBject(result, functionNode);
-  //       break;
-  //   }
-  // });
-  return result;
+  let graphLogicChunks: QueryGraphNodes[][] = [[]];
+
+  const entities: EntityNodeAttributes[] = graph.nodes
+    .map((n) => n.attributes)
+    .filter((n) => n?.type === QueryElementTypes.Entity) as EntityNodeAttributes[];
+  const entitiesEmptyLeftHandle = entities.filter((n) => !graph.edges.some((e) => e.target === n?.id));
+  // const entitiesEmptyRightHandle = entities.filter((n) => !n?.rightRelationHandleId);
+
+  const relations: RelationNodeAttributes[] = graph.nodes
+    .map((n) => n.attributes)
+    .filter((n) => n?.type === QueryElementTypes.Relation) as RelationNodeAttributes[];
+  // const relationsEmptyLeftHandle = relations.filter((n) => !n?.leftEntityHandleId);
+  // const relationsEmptyRightHandle = relations.filter((n) => !n?.rightEntityHandleId);
+
+  const traversePaths = (node: QueryGraphNodes, paths: QueryGraphNodes[][], currentIdx: number): number => {
+    if (!paths?.[currentIdx]) paths.push([]);
+    paths[currentIdx].push(node);
+
+    // const rightHandle =
+    //   node.type === QueryElementTypes.Entity
+    //     ? (node as EntityNodeAttributes)?.rightRelationHandleId
+    //     : (node as RelationNodeAttributes)?.rightEntityHandleId;
+
+    let connections = graph.edges.filter((e) => e.source === node.id);
+    if (connections.length === 0) return 0;
+
+    const nodesToRight = connections
+      .map((c, i) => {
+        const rightNodeHandleData = fromHandleId(c.attributes?.targetHandle);
+        const rightNode =
+          rightNodeHandleData.nodeType === QueryElementTypes.Entity
+            ? entities.find((r) => r.id === c.target)
+            : relations.find((r) => r.id === c.target);
+        return rightNode;
+      })
+      .filter((n) => n !== undefined) as QueryGraphNodes[];
+
+    // if (!rightNode) return;
+    // if (currentIdx + i === paths.length) paths.push(paths[currentIdx - 1 + i]);
+    // inverseTraverse(rightNode, paths, currentIdx + i);
+    // paths[currentIdx + i].push(rightNode);
+    // nodesToRight.forEach((rightNode, i) => {
+    //   if (currentIdx + i === paths.length) {
+    //     paths.push(paths[currentIdx - 1 + i]);
+    //   }
+    // });
+
+    let chunkOffset = 0;
+    nodesToRight.forEach((rightNode, i) => {
+      chunkOffset += traversePaths(rightNode, paths, currentIdx + i + chunkOffset);
+    });
+
+    return nodesToRight.length - 1; // offset
+  };
+
+  // find paths which ends at an entity with no right handle
+  let chunkOffset = 0;
+  entitiesEmptyLeftHandle.map((entity, i) => {
+    chunkOffset += traversePaths(entity, graphLogicChunks, i + chunkOffset);
+  });
+
+  console.log('graphLogicChunks', graphLogicChunks);
+  if (!graphLogicChunks || graphLogicChunks.length === 0 || graphLogicChunks?.[0].length === 0) return query;
+
+  const processConnection = (chunk: QueryGraphNodes[], position: number): RelationStruct | NodeStruct => {
+    const currNode = chunk[position];
+
+    if (currNode.type === QueryElementTypes.Relation) {
+      const ret: RelationStruct = {
+        ID: currNode?.id,
+        label: currNode?.name,
+        // depth: QuerySearchDepthStruct;
+        direction: 'TO',
+        node: chunk.length === position + 1 ? undefined : (processConnection(chunk, position + 1) as NodeStruct),
+      };
+      return ret;
+    } else if (currNode.type === QueryElementTypes.Entity) {
+      const ret: NodeStruct = {
+        ID: currNode?.id,
+        label: currNode?.name,
+        // logic: LogicStruct[];
+        // subQuery?: QueryStruct;
+        // export: ExportNodeStruct[];
+        relation: chunk.length === position + 1 ? undefined : (processConnection(chunk, position + 1) as RelationStruct),
+      };
+      return ret;
+    }
+
+    throw Error('Malformed Chunks! ' + chunk + position);
+  };
+
+  query.query = graphLogicChunks.map((chunk, i) => {
+    const ret: QueryStruct = {
+      ID: 'path_' + i, //TODO: chunk[0].id ||
+      node: processConnection(chunk.reverse(), 0),
+    };
+    return ret;
+  });
+
+  console.log(query);
+
+  return query;
 }
+
+// function processConnectionFromRelation(
+//   c: SerializedEdge,
+//   entities: EntityNodeAttributes[],
+//   relations: RelationNodeAttributes[]
+// ): NodeStruct {
+//   if (c.attributes?.targetHandle === null) throw Error('Malformed Graph! One or more edges of a relation node do not exist');
+//   const targetHandleData = fromHandleId(c.attributes?.targetHandle);
+
+//   if (targetHandleData.nodeType === QueryElementTypes.Entity) {
+//     const targetEntity = entities.find((r) => r.id === c.target);
+//   } else if (targetHandleData.nodeType === QueryElementTypes.Relation) {
+//     const targetRelation = relations.find((r) => r.id === c.target);
+//   } else if (targetHandleData.nodeType === QueryElementTypes.Logic) {
+//   }
+// }
diff --git a/libs/shared/lib/schema/index.ts b/libs/shared/lib/schema/index.ts
index 3c5fd8443..9f8ccaddf 100644
--- a/libs/shared/lib/schema/index.ts
+++ b/libs/shared/lib/schema/index.ts
@@ -1,2 +1 @@
 export * from './model';
-export * from '../querybuilder/model';
diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx
index 7245b9eab..2c5ee5dee 100644
--- a/libs/shared/lib/schema/panel/schema.tsx
+++ b/libs/shared/lib/schema/panel/schema.tsx
@@ -15,6 +15,7 @@ import ReactFlow, {
   ReactFlowInstance,
   Background,
 } from 'reactflow';
+import CachedIcon from '@mui/icons-material/Cached';
 
 import 'reactflow/dist/style.css';
 
@@ -216,6 +217,15 @@ export const Schema = (props: Props) => {
             >
               <img src={exportIcon} width={21}></img>
             </ControlButton> */}
+            <ControlButton
+              className={styles.exportButton}
+              title={'Refresh graph schema'}
+              onClick={(event) => {
+                event.stopPropagation();
+              }}
+            >
+              <CachedIcon />
+            </ControlButton>
           </Controls>
         </ReactFlow>
       </ReactFlowProvider>
diff --git a/libs/shared/lib/vis/nodelink/nodelinkvis.tsx b/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
index 8771f7e94..3e28c34c9 100644
--- a/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
+++ b/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
@@ -63,7 +63,6 @@ export const NodeLinkVis = React.memo((props: Props) => {
   const graphQueryResult = useGraphQueryResult();
   const dispatch = useAppDispatch();
   const theme = useTheme();
-  console.log('update nodelink');
 
   useEffect(() => {
     console.log('update nodelink useEffect', graphQueryResult);
diff --git a/libs/shared/node.d.ts b/libs/shared/node.d.ts
index 0cb37864e..3d2ced5bc 100644
--- a/libs/shared/node.d.ts
+++ b/libs/shared/node.d.ts
@@ -1,6 +1,7 @@
 interface ImportMeta {
   env: {
     VITE_BACKEND_URL: string;
+    VITE_BACKEND_WSS_URL: string;
     VITE_BACKEND_USER: string;
     VITE_BACKEND_SCHEMA: string;
     VITE_BACKEND_QUERY: string;
diff --git a/libs/shared/tsconfig.json b/libs/shared/tsconfig.json
index 855b96076..b2617928c 100644
--- a/libs/shared/tsconfig.json
+++ b/libs/shared/tsconfig.json
@@ -1,34 +1,34 @@
-{
-  "$schema": "https://json.schemastore.org/tsconfig",
-  "display": "React Library",
-  "target": "ESNext",
-  "compilerOptions": {
-    "composite": false,
-    "inlineSources": false,
-    "noUnusedLocals": false,
-    "noUnusedParameters": false,
-    "preserveWatchOutput": true,
-    "jsx": "react-jsx",
-    "target": "ESNext",
-    "useDefineForClassFields": true,
-    "lib": ["ES2017", "DOM", "DOM.Iterable", "ESNext"],
-    "allowJs": true,
-    "skipLibCheck": true,
-    "esModuleInterop": true,
-    "allowSyntheticDefaultImports": true,
-    "strict": true,
-    "forceConsistentCasingInFileNames": true,
-    "module": "ESNext",
-    "moduleResolution": "node",
-    "resolveJsonModule": true,
-    "noEmit": true,
-    "baseUrl": ".",
-    "paths": {
-      "@graphpolaris/shared/lib/*": ["./lib/*"]
-    }
-  },
-  "exclude": ["dist", "build", "node_modules"],
-  "include": ["src", "lib"],
-  "files": ["./node.d.ts"],
-  "references": [{ "path": "./tsconfig.node.json" }]
-}
+{
+  "$schema": "https://json.schemastore.org/tsconfig",
+  "display": "React Library",
+  "target": "ESNext",
+  "compilerOptions": {
+    "composite": false,
+    "inlineSources": false,
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+    "preserveWatchOutput": true,
+    "jsx": "react-jsx",
+    "target": "ESNext",
+    "useDefineForClassFields": true,
+    "lib": ["ES2017", "DOM", "DOM.Iterable", "ESNext"],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
+    "strict": true,
+    "forceConsistentCasingInFileNames": true,
+    "module": "ESNext",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "noEmit": true,
+    "baseUrl": ".",
+    "paths": {
+      "@graphpolaris/shared/lib/*": ["./lib/*"]
+    }
+  },
+  "exclude": ["dist", "build", "node_modules"],
+  "include": ["src", "lib"],
+  "files": ["./node.d.ts"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/package.json b/package.json
index 38926ee17..c9e130644 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,8 @@
     "eslint-config-custom": "workspace:*",
     "husky": "^8.0.0",
     "prettier": "latest",
-    "turbo": "latest"
+    "turbo": "latest",
+    "vitest": "^0.29.2"
   },
   "engines": {
     "node": ">=14.0.0"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5f6eec94c..64a3fc97a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -25,10 +25,13 @@ importers:
         version: 8.0.3
       prettier:
         specifier: latest
-        version: 2.8.8
+        version: 3.0.0
       turbo:
         specifier: latest
-        version: 1.10.3
+        version: 1.10.8
+      vitest:
+        specifier: ^0.29.2
+        version: 0.29.4(happy-dom@8.9.0)(jsdom@21.1.1)(sass@1.59.3)
 
   apps/docs:
     dependencies:
@@ -557,7 +560,7 @@ importers:
         version: 8.7.0(eslint@7.32.0)
       eslint-config-turbo:
         specifier: latest
-        version: 1.10.3(eslint@7.32.0)
+        version: 1.10.6(eslint@7.32.0)
       eslint-plugin-react:
         specifier: 7.31.8
         version: 7.31.8(eslint@7.32.0)
@@ -8893,7 +8896,6 @@ packages:
   /dotenv@16.0.3:
     resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
     engines: {node: '>=12'}
-    dev: true
 
   /draco3d@1.5.5:
     resolution: {integrity: sha512-JVuNV0EJzD3LBYhGyIXJLeBID/EVtmFO1ZNhAYflTgiMiAJlbhXQmRRda/azjc8MRVMHh0gqGhiqHUo5dIXM8Q==}
@@ -9224,15 +9226,6 @@ packages:
     dependencies:
       eslint: 7.32.0
 
-  /eslint-config-turbo@1.10.3(eslint@7.32.0):
-    resolution: {integrity: sha512-ggzPfTJfMsMS383oZ4zfTP1zQvyMyiigOQJRUnLt1nqII6SKkTzdKZdwmXRDHU24KFwUfEFtT6c8vnm2VhL0uQ==}
-    peerDependencies:
-      eslint: '>6.6.0'
-    dependencies:
-      eslint: 7.32.0
-      eslint-plugin-turbo: 1.10.3(eslint@7.32.0)
-    dev: false
-
   /eslint-config-turbo@1.10.6(eslint@7.32.0):
     resolution: {integrity: sha512-iZ63etePRUdEIDY5MxdUhU2ekV9TDbVdHg0BK00QqVFgQTXUYuJ7rsQj/wUKTsw3jwhbLfaY6H5sknAgYyWZ2g==}
     peerDependencies:
@@ -9240,7 +9233,6 @@ packages:
     dependencies:
       eslint: 7.32.0
       eslint-plugin-turbo: 1.10.6(eslint@7.32.0)
-    dev: true
 
   /eslint-import-resolver-node@0.3.7:
     resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==}
@@ -9383,14 +9375,6 @@ packages:
       semver: 6.3.0
       string.prototype.matchall: 4.0.8
 
-  /eslint-plugin-turbo@1.10.3(eslint@7.32.0):
-    resolution: {integrity: sha512-g3Mnnk7el1FqxHfqbE/MayLvCsYjA/vKmAnUj66kV4AlM7p/EZqdt42NMcMSKtDVEm0w+utQkkzWG2Xsa0Pd/g==}
-    peerDependencies:
-      eslint: '>6.6.0'
-    dependencies:
-      eslint: 7.32.0
-    dev: false
-
   /eslint-plugin-turbo@1.10.6(eslint@7.32.0):
     resolution: {integrity: sha512-jlzfxYaK8hcz1DTk8Glxxi1r0kgdy85191a4CbFOTiiBulmKHMLJgzhsyE9Ong796MA62n91KFpc20BiKjlHwg==}
     peerDependencies:
@@ -9398,7 +9382,6 @@ packages:
     dependencies:
       dotenv: 16.0.3
       eslint: 7.32.0
-    dev: true
 
   /eslint-scope@5.1.1:
     resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
@@ -12501,6 +12484,12 @@ packages:
     hasBin: true
     dev: true
 
+  /prettier@3.0.0:
+    resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==}
+    engines: {node: '>=14'}
+    hasBin: true
+    dev: true
+
   /pretty-format@27.5.1:
     resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
     engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@@ -14327,65 +14316,65 @@ packages:
       tslib: 1.14.1
       typescript: 4.9.5
 
-  /turbo-darwin-64@1.10.3:
-    resolution: {integrity: sha512-IIB9IomJGyD3EdpSscm7Ip1xVWtYb7D0x7oH3vad3gjFcjHJzDz9xZ/iw/qItFEW+wGFcLSRPd+1BNnuLM8AsA==}
+  /turbo-darwin-64@1.10.8:
+    resolution: {integrity: sha512-FOK3qrLZE2Yq7/2DkAnAzghisGQroZJs85Rui3IXM/2e7rTtBADmU9w36d4k0Yw7RHEiOo8U4eAYUl52OWRwJQ==}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
     dev: true
     optional: true
 
-  /turbo-darwin-arm64@1.10.3:
-    resolution: {integrity: sha512-SBNmOZU9YEB0eyNIxeeQ+Wi0Ufd+nprEVp41rgUSRXEIpXjsDjyBnKnF+sQQj3+FLb4yyi/yZQckB+55qXWEsw==}
+  /turbo-darwin-arm64@1.10.8:
+    resolution: {integrity: sha512-8mbgH8oBycusa8RnbHlvrpHxfZsgNrk6CXMu/KJECpajYT3nSOMK2Rrs+422HqLDTVUw4GAqmTr26nUx8yJoyA==}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
     dev: true
     optional: true
 
-  /turbo-linux-64@1.10.3:
-    resolution: {integrity: sha512-kvAisGKE7xHJdyMxZLvg53zvHxjqPK1UVj4757PQqtx9dnjYHSc8epmivE6niPgDHon5YqImzArCjVZJYpIGHQ==}
+  /turbo-linux-64@1.10.8:
+    resolution: {integrity: sha512-eJ1ND3LuILw28gd+9f3Ews7Eika9WOxp+/PxJI+EPHseTrbLMLYqSPAunmZdOx840Pq0Sk5j4Nik7NCzuCWXkg==}
     cpu: [x64]
     os: [linux]
     requiresBuild: true
     dev: true
     optional: true
 
-  /turbo-linux-arm64@1.10.3:
-    resolution: {integrity: sha512-Qgaqln0IYRgyL0SowJOi+PNxejv1I2xhzXOI+D+z4YHbgSx87ox1IsALYBlK8VRVYY8VCXl+PN12r1ioV09j7A==}
+  /turbo-linux-arm64@1.10.8:
+    resolution: {integrity: sha512-3+pVaOzGP/5GFvQakxuHDMsj43Y6bmaq5/84tvgGL0FgtKpsQvBfdaDs12HX5cb/zUnd2/jdQPNiGJwVeC/McA==}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
     dev: true
     optional: true
 
-  /turbo-windows-64@1.10.3:
-    resolution: {integrity: sha512-rbH9wManURNN8mBnN/ZdkpUuTvyVVEMiUwFUX4GVE5qmV15iHtZfDLUSGGCP2UFBazHcpNHG1OJzgc55GFFrUw==}
+  /turbo-windows-64@1.10.8:
+    resolution: {integrity: sha512-LdryI+ZQsVrW4hWZw5G5vJz0syjWxyc0tnieZRefy+d9Ti1du/qCYLP0KQRgL9Yuh1klbH/tzmx70upGARgWKQ==}
     cpu: [x64]
     os: [win32]
     requiresBuild: true
     dev: true
     optional: true
 
-  /turbo-windows-arm64@1.10.3:
-    resolution: {integrity: sha512-ThlkqxhcGZX39CaTjsHqJnqVe+WImjX13pmjnpChz6q5HHbeRxaJSFzgrHIOt0sUUVx90W/WrNRyoIt/aafniw==}
+  /turbo-windows-arm64@1.10.8:
+    resolution: {integrity: sha512-whHnhM84KIa2Ly/fcw2Ujw2Rr/9wh8ynAdZ9bdvZoZKAbOr3tXKft0tmy50jQ6IsNr6Cj0XD4cuSTKhvqoGtYA==}
     cpu: [arm64]
     os: [win32]
     requiresBuild: true
     dev: true
     optional: true
 
-  /turbo@1.10.3:
-    resolution: {integrity: sha512-U4gKCWcKgLcCjQd4Pl8KJdfEKumpyWbzRu75A6FCj6Ctea1PIm58W6Ltw1QXKqHrl2pF9e1raAskf/h6dlrPCA==}
+  /turbo@1.10.8:
+    resolution: {integrity: sha512-lmPKkeRMC/3gjTVxICt93A8zAzjGjbZINdekjzivn4g/rOjpHVNuOuVANU5L4H4R1bzQr8FFvZNQeQaElOjz/Q==}
     hasBin: true
     requiresBuild: true
     optionalDependencies:
-      turbo-darwin-64: 1.10.3
-      turbo-darwin-arm64: 1.10.3
-      turbo-linux-64: 1.10.3
-      turbo-linux-arm64: 1.10.3
-      turbo-windows-64: 1.10.3
-      turbo-windows-arm64: 1.10.3
+      turbo-darwin-64: 1.10.8
+      turbo-darwin-arm64: 1.10.8
+      turbo-linux-64: 1.10.8
+      turbo-linux-arm64: 1.10.8
+      turbo-windows-64: 1.10.8
+      turbo-windows-arm64: 1.10.8
     dev: true
 
   /type-check@0.3.2:
-- 
GitLab