diff --git a/app/web/package.json b/app/web/package.json index c7019d1bc5..57a4ab2a42 100644 --- a/app/web/package.json +++ b/app/web/package.json @@ -54,6 +54,10 @@ "date-fns": "^2.29.2", "floating-vue": "^2.0.0-beta.16", "fontfaceobserver": "^2.3.0", + "graphology": "^0.25.4", + "graphology-layout": "^0.6.1", + "graphology-layout-forceatlas2": "^0.10.1", + "graphology-layout-noverlap": "^0.4.2", "is-promise": "^4.0.0", "javascript-time-ago": "^2.5.7", "js-base64": "^3.7.5", @@ -70,6 +74,7 @@ "plur": "^5.1.0", "posthog-js": "^1.76.0", "reconnecting-websocket": "^4.4.0", + "sigma": "3.0.0-beta.2", "tinycolor2": "^1.4.2", "typescript": "^4.9.5", "validator": "^13.7.0", @@ -79,10 +84,10 @@ "vue-router": "^4.1.6", "vue-safe-teleport": "^0.1.2", "vue-toastification": "2.0.0-rc.5", - "yjs-codemirror-plugin": "workspace:*", "y-indexeddb": "^9.0.12", "y-websocket": "^1.5.0", - "yjs": "^13.6.8" + "yjs": "^13.6.8", + "yjs-codemirror-plugin": "workspace:*" }, "devDependencies": { "@iconify/json": "^2.2.135", @@ -101,6 +106,7 @@ "cypress": "^9.6.0", "eslint": "^8.36.0", "faker": "^6.6.6", + "graphology-types": "^0.24.7", "unplugin-icons": "^0.17.1", "vite": "^4.4.9", "vite-plugin-checker": "^0.6.1", @@ -110,4 +116,4 @@ "volta": { "extends": "../../package.json" } -} \ No newline at end of file +} diff --git a/app/web/src/components/Workspace/WorkspaceViz.vue b/app/web/src/components/Workspace/WorkspaceViz.vue new file mode 100644 index 0000000000..8a16061ea8 --- /dev/null +++ b/app/web/src/components/Workspace/WorkspaceViz.vue @@ -0,0 +1,31 @@ + + + diff --git a/app/web/src/components/Workspace/WorkspaceVizSchemaVariant.vue b/app/web/src/components/Workspace/WorkspaceVizSchemaVariant.vue new file mode 100644 index 0000000000..f008e4b984 --- /dev/null +++ b/app/web/src/components/Workspace/WorkspaceVizSchemaVariant.vue @@ -0,0 +1,95 @@ + + + diff --git a/app/web/src/router.ts b/app/web/src/router.ts index 41832512aa..c891ea49f8 100644 --- a/app/web/src/router.ts +++ b/app/web/src/router.ts @@ -49,6 +49,11 @@ const routes: RouteRecordRaw[] = [ }; }, }, + { + path: ":changeSetId/viz", + name: "workspace-viz", + component: () => import("@/components/Workspace/WorkspaceViz.vue"), + }, { path: ":changeSetId/c", name: "workspace-compose", diff --git a/app/web/src/store/viz.store.ts b/app/web/src/store/viz.store.ts new file mode 100644 index 0000000000..d091404b7e --- /dev/null +++ b/app/web/src/store/viz.store.ts @@ -0,0 +1,81 @@ +import { defineStore } from "pinia"; +import { ApiRequest, addStoreHooks } from "@si/vue-lib/pinia"; +import { useWorkspacesStore } from "@/store/workspaces.store"; +import { useChangeSetsStore } from "@/store/change_sets.store"; +import { Visibility } from "@/api/sdf/dal/visibility"; +import { nilId } from "@/utils/nilId"; + +export type NodeKind = "Category" | "Content" | "Func" | "Ordering" | "Prop"; + +export type ContentKind = + | "Root" + | "ActionPrototype" + | "AttributePrototype" + | "AttributePrototypeArgument" + | "AttributeValue" + | "Component" + | "ExternalProvider" + | "FuncArg" + | "Func" + | "InternalProvider" + | "Prop" + | "Schema" + | "SchemaVariant" + | "StaticArgumentValue" + | "ValidationPrototype"; + +export interface VizResponse { + edges: { + from: string; + to: string; + }[]; + + nodes: { + id: string; + nodeKind: NodeKind; + contentKind: ContentKind | null; + name: string | null; + }[]; + + rootNodeId: string; +} + +export const useVizStore = () => { + const changeSetStore = useChangeSetsStore(); + const selectedChangeSetId = changeSetStore.selectedChangeSetId; + const workspacesStore = useWorkspacesStore(); + const workspaceId = workspacesStore.selectedWorkspacePk; + const visibility: Visibility = { + visibility_change_set_pk: selectedChangeSetId ?? nilId(), + }; + + return addStoreHooks( + defineStore( + `ws${workspaceId || "NONE"}/cs${selectedChangeSetId || "NONE"}/viz`, + { + state: () => ({ + edges: [], + nodes: [], + }), + getters: { + nodes: (state) => state.nodes, + edges: (state) => state.edges, + }, + actions: { + async FETCH_VIZ() { + return new ApiRequest({ + url: "/graphviz/nodes_edges", + params: { ...visibility }, + }); + }, + async FETCH_SCHEMA_VARIANT_VIZ(schemaVariantId: string) { + return new ApiRequest({ + url: "/graphviz/schema_variant", + params: { schemaVariantId, ...visibility }, + }); + }, + }, + }, + ), + )(); +}; diff --git a/lib/dal/src/workspace_snapshot.rs b/lib/dal/src/workspace_snapshot.rs index 23600f6ce7..8f18173f59 100644 --- a/lib/dal/src/workspace_snapshot.rs +++ b/lib/dal/src/workspace_snapshot.rs @@ -215,6 +215,10 @@ impl WorkspaceSnapshot { self.id } + pub fn root(&mut self) -> WorkspaceSnapshotResult { + Ok(self.working_copy()?.root()) + } + fn working_copy(&mut self) -> WorkspaceSnapshotResult<&mut WorkspaceSnapshotGraph> { if self.working_copy.is_none() { self.working_copy = Some(postcard::from_bytes(&self.snapshot)?); @@ -362,6 +366,18 @@ impl WorkspaceSnapshot { Ok(()) } + pub fn nodes( + &mut self, + ) -> WorkspaceSnapshotResult> { + Ok(self.working_copy()?.nodes()) + } + + pub fn edges( + &mut self, + ) -> WorkspaceSnapshotResult> { + Ok(self.working_copy()?.edges()) + } + pub fn dot(&mut self) { self.working_copy() .expect("failed on accessing or creating a working copy") @@ -491,6 +507,42 @@ impl WorkspaceSnapshot { .collect()) } + pub fn all_outgoing_targets( + &mut self, + id: impl Into, + ) -> WorkspaceSnapshotResult> { + let mut result = vec![]; + let target_idxs: Vec = self + .edges_directed(id, Direction::Outgoing)? + .map(|edge_ref| edge_ref.target()) + .collect(); + + for target_idx in target_idxs { + let node_weight = self.get_node_weight(target_idx)?; + result.push(node_weight.to_owned()); + } + + Ok(result) + } + + pub fn all_incoming_sources( + &mut self, + id: impl Into, + ) -> WorkspaceSnapshotResult> { + let mut result = vec![]; + let source_idxs: Vec = self + .edges_directed(id, Direction::Incoming)? + .map(|edge_ref| edge_ref.source()) + .collect(); + + for source_idx in source_idxs { + let node_weight = self.get_node_weight(source_idx)?; + result.push(node_weight.to_owned()); + } + + Ok(result) + } + pub fn remove_incoming_edges_of_kind( &mut self, change_set: &ChangeSetPointer, diff --git a/lib/dal/src/workspace_snapshot/graph.rs b/lib/dal/src/workspace_snapshot/graph.rs index 6c5e8f946c..4d701c3f2a 100644 --- a/lib/dal/src/workspace_snapshot/graph.rs +++ b/lib/dal/src/workspace_snapshot/graph.rs @@ -233,6 +233,27 @@ impl WorkspaceSnapshotGraph { self.graph.edges_directed(node_index, direction) } + pub fn nodes(&self) -> impl Iterator { + self.graph.node_indices().filter_map(|node_idx| { + self.graph + .node_weight(node_idx) + .map(|weight| (weight, node_idx)) + }) + } + + pub fn edges(&self) -> impl Iterator { + self.graph.edge_indices().filter_map(|edge_idx| { + self.graph + .edge_weight(edge_idx) + .map(|weight| { + self.graph + .edge_endpoints(edge_idx) + .map(|(source, target)| (weight, source, target)) + }) + .flatten() + }) + } + pub fn add_ordered_edge( &mut self, change_set: &ChangeSetPointer, diff --git a/lib/dal/src/workspace_snapshot/node_weight/category_node_weight.rs b/lib/dal/src/workspace_snapshot/node_weight/category_node_weight.rs index 994b9cb950..1b4061ef3b 100644 --- a/lib/dal/src/workspace_snapshot/node_weight/category_node_weight.rs +++ b/lib/dal/src/workspace_snapshot/node_weight/category_node_weight.rs @@ -1,13 +1,14 @@ use chrono::{DateTime, Utc}; use content_store::ContentHash; use serde::{Deserialize, Serialize}; +use strum::Display; use ulid::Ulid; use crate::change_set_pointer::ChangeSetPointer; use crate::workspace_snapshot::vector_clock::VectorClockId; use crate::workspace_snapshot::{node_weight::NodeWeightResult, vector_clock::VectorClock}; -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Display)] pub enum CategoryNodeKind { Component, Func, diff --git a/lib/sdf-server/src/server/routes.rs b/lib/sdf-server/src/server/routes.rs index d84e6d9b59..e133c2b510 100644 --- a/lib/sdf-server/src/server/routes.rs +++ b/lib/sdf-server/src/server/routes.rs @@ -33,14 +33,15 @@ pub fn routes(state: AppState) -> Router { ) .nest("/api/func", crate::server::service::func::routes()) .nest("/api/schema", crate::server::service::schema::routes()) - .nest("/api/diagram", crate::server::service::diagram::routes()); - // .nest("/api/fix", crate::server::service::fix::routes()) - // .nest("/api/pkg", crate::server::service::pkg::routes()) - // .nest("/api/provider", crate::server::service::provider::routes()) - // .nest( - // "/api/qualification", - // crate::server::service::qualification::routes(), - // ) + // .nest("/api/fix", crate::server::service::fix::routes()) + // .nest("/api/pkg", crate::server::service::pkg::routes()) + // .nest("/api/provider", crate::server::service::provider::routes()) + // .nest( + // "/api/qualification", + // crate::server::service::qualification::routes(), + // ) + .nest("/api/diagram", crate::server::service::diagram::routes()) + .nest("/api/graphviz", crate::server::service::graphviz::routes()); // .nest("/api/secret", crate::server::service::secret::routes()) // .nest("/api/status", crate::server::service::status::routes()) // .nest( diff --git a/lib/sdf-server/src/server/service.rs b/lib/sdf-server/src/server/service.rs index 4e0d4ede90..f49fa9ecc3 100644 --- a/lib/sdf-server/src/server/service.rs +++ b/lib/sdf-server/src/server/service.rs @@ -2,6 +2,7 @@ pub mod change_set; pub mod component; pub mod diagram; pub mod func; +pub mod graphviz; pub mod schema; pub mod session; pub mod ws; diff --git a/lib/sdf-server/src/server/service/graphviz.rs b/lib/sdf-server/src/server/service/graphviz.rs new file mode 100644 index 0000000000..1381697e7b --- /dev/null +++ b/lib/sdf-server/src/server/service/graphviz.rs @@ -0,0 +1,308 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + +use axum::{extract::Query, response::Response, routing::get, Json, Router}; +use dal::{ + schema::variant::SchemaVariantError, + workspace_snapshot::{ + self, + content_address::ContentAddressDiscriminants, + edge_weight::EdgeWeightKindDiscriminants, + node_weight::{NodeWeight, NodeWeightDiscriminants}, + WorkspaceSnapshotError, + }, + SchemaVariant, SchemaVariantId, TransactionsError, Visibility, +}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use ulid::Ulid; + +use crate::server::{ + extract::{AccessBuilder, HandlerContext}, + impl_default_error_into_response, + state::AppState, +}; + +#[remain::sorted] +#[derive(Error, Debug)] +pub enum GraphVizError { + #[error(transparent)] + ContextTransaction(#[from] TransactionsError), + #[error("graph did not have a root node, although this is an unreachable state")] + NoRootNode, + #[error(transparent)] + SchemaVariant(#[from] SchemaVariantError), + #[error("could not acquire lock: {0}")] + TryLock(#[from] tokio::sync::TryLockError), + #[error("workspace snapshot error")] + WorkspaceSnapshot(#[from] WorkspaceSnapshotError), +} + +type GraphVizResult = Result; + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GraphVizRequest { + #[serde(flatten)] + pub visibility: Visibility, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct SchemaVariantVizRequest { + #[serde(flatten)] + pub visibility: Visibility, + pub schema_variant_id: SchemaVariantId, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GraphVizNode { + id: Ulid, + content_kind: Option, + node_kind: NodeWeightDiscriminants, + name: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GraphVizEdge { + from: Ulid, + to: Ulid, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GraphVizResponse { + pub nodes: Vec, + pub edges: Vec, + pub root_node_id: Ulid, +} + +pub async fn schema_variant( + HandlerContext(builder): HandlerContext, + AccessBuilder(request_ctx): AccessBuilder, + Query(request): Query, +) -> GraphVizResult> { + let ctx = builder.build(request_ctx.build(request.visibility)).await?; + + let mut func_nodes = vec![]; + let mut nodes = vec![]; + let mut edges = vec![]; + let mut added_nodes = HashSet::new(); + let mut added_edges = HashSet::new(); + let mut root_node_id: Option = None; + + let sv = SchemaVariant::get_by_id(&ctx, request.schema_variant_id).await?; + + let sv_node = { + let mut workspace_snapshot = ctx.workspace_snapshot()?.try_lock()?; + let node_idx = workspace_snapshot.get_node_index_by_id(request.schema_variant_id)?; + let sv_node_weight = workspace_snapshot.get_node_weight(node_idx)?; + + added_nodes.insert(sv_node_weight.id()); + GraphVizNode { + id: sv_node_weight.id(), + content_kind: sv_node_weight.content_address_discriminants(), + node_kind: sv_node_weight.into(), + name: Some(sv.name().to_owned()), + } + }; + + nodes.push(sv_node); + + // descend + let mut work_queue: VecDeque = VecDeque::from([request.schema_variant_id.into()]); + while let Some(id) = work_queue.pop_front() { + let mut workspace_snapshot = ctx.workspace_snapshot()?.try_lock()?; + for target in workspace_snapshot.all_outgoing_targets(id)? { + work_queue.push_back(target.id()); + if !added_edges.contains(&(id, target.id())) { + added_edges.insert((id, target.id())); + edges.push(GraphVizEdge { + from: id, + to: target.id(), + }); + } + let name = match &target { + NodeWeight::Category(inner) => Some(inner.kind().to_string()), + NodeWeight::Func(inner) => { + func_nodes.push(inner.id()); + Some(inner.name().to_owned()) + } + NodeWeight::Prop(inner) => Some(inner.name().to_owned()), + _ => None, + }; + + if !added_nodes.contains(&target.id()) { + added_nodes.insert(target.id()); + nodes.push(GraphVizNode { + id: target.id(), + content_kind: target.content_address_discriminants(), + node_kind: target.into(), + name, + }) + } + } + } + + // ascend + let mut work_queue: VecDeque = VecDeque::from([request.schema_variant_id.into()]); + while let Some(id) = work_queue.pop_front() { + let mut workspace_snapshot = ctx.workspace_snapshot()?.try_lock()?; + let sources = workspace_snapshot.all_incoming_sources(id)?; + if sources.is_empty() { + root_node_id = Some(id); + continue; + } + + for source in sources { + work_queue.push_back(source.id()); + if !added_edges.contains(&(source.id(), id)) { + added_edges.insert((source.id(), id)); + edges.push(GraphVizEdge { + from: source.id(), + to: id, + }); + } + + let name = match &source { + NodeWeight::Category(inner) => Some(inner.kind().to_string()), + NodeWeight::Func(inner) => Some(inner.name().to_owned()), + NodeWeight::Prop(inner) => Some(inner.name().to_owned()), + _ => None, + }; + + if !added_nodes.contains(&source.id()) { + added_nodes.insert(source.id()); + nodes.push(GraphVizNode { + id: source.id(), + content_kind: source.content_address_discriminants(), + node_kind: source.into(), + name, + }) + } + } + } + + // connect func_nodes to root + for func_id in func_nodes { + let mut workspace_snapshot = ctx.workspace_snapshot()?.try_lock()?; + for user_node_idx in workspace_snapshot + .incoming_sources_for_edge_weight_kind(func_id, EdgeWeightKindDiscriminants::Use)? + { + let user_node = workspace_snapshot + .get_node_weight(user_node_idx)? + .to_owned(); + + if let NodeWeight::Category(cat_inner) = &user_node { + let name = Some(cat_inner.kind().to_string()); + if !added_edges.contains(&(func_id, cat_inner.id())) { + added_edges.insert((func_id, cat_inner.id())); + edges.push(GraphVizEdge { + from: cat_inner.id(), + to: func_id, + }); + } + if !added_nodes.contains(&cat_inner.id()) { + added_nodes.insert(cat_inner.id()); + nodes.push(GraphVizNode { + id: cat_inner.id(), + content_kind: user_node.content_address_discriminants(), + node_kind: user_node.to_owned().into(), + name, + }) + } + for cat_user_node_idx in workspace_snapshot.incoming_sources_for_edge_weight_kind( + user_node.id(), + EdgeWeightKindDiscriminants::Use, + )? { + let node_weight = workspace_snapshot.get_node_weight(cat_user_node_idx)?; + match node_weight + .get_content_node_weight_of_kind(ContentAddressDiscriminants::Root) + { + Ok(root_content) => { + if !added_edges.contains(&(cat_inner.id(), root_content.id())) { + added_edges.insert((cat_inner.id(), root_content.id())); + edges.push(GraphVizEdge { + from: root_content.id(), + to: cat_inner.id(), + }); + } + } + _ => continue, + } + } + } + } + } + + let root_node_id = root_node_id.ok_or(GraphVizError::NoRootNode)?; + + Ok(Json(GraphVizResponse { + nodes, + edges, + root_node_id, + })) +} + +pub async fn nodes_edges( + HandlerContext(builder): HandlerContext, + AccessBuilder(request_ctx): AccessBuilder, + Query(request): Query, +) -> GraphVizResult> { + let ctx = builder.build(request_ctx.build(request.visibility)).await?; + + let mut workspace_snapshot = ctx.workspace_snapshot()?.try_lock()?; + + let mut node_idx_to_id = HashMap::new(); + + let root_node_idx = workspace_snapshot.root()?; + + let nodes = workspace_snapshot + .nodes()? + .map(|(weight, idx)| { + node_idx_to_id.insert(idx, weight.id()); + let name = match weight { + NodeWeight::Category(inner) => Some(inner.kind().to_string()), + NodeWeight::Func(inner) => Some(inner.name().to_owned()), + NodeWeight::Prop(inner) => Some(inner.name().to_owned()), + _ => None, + }; + GraphVizNode { + id: weight.id(), + content_kind: weight.content_address_discriminants(), + node_kind: weight.into(), + name, + } + }) + .collect(); + + let edges = workspace_snapshot + .edges()? + .filter_map( + |(_, from, to)| match (node_idx_to_id.get(&from), node_idx_to_id.get(&to)) { + (None, _) | (_, None) => None, + (Some(&from), Some(&to)) => Some(GraphVizEdge { from, to }), + }, + ) + .collect(); + + let response = GraphVizResponse { + nodes, + edges, + root_node_id: node_idx_to_id + .get(&root_node_idx) + .copied() + .ok_or(GraphVizError::NoRootNode)?, + }; + + Ok(Json(response)) +} + +impl_default_error_into_response!(GraphVizError); + +pub fn routes() -> Router { + Router::new() + .route("/schema_variant", get(schema_variant)) + .route("/nodes_edges", get(nodes_edges)) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4e492fc88..0c7285008f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,6 +185,18 @@ importers: fontfaceobserver: specifier: ^2.3.0 version: 2.3.0 + graphology: + specifier: ^0.25.4 + version: 0.25.4(graphology-types@0.24.7) + graphology-layout: + specifier: ^0.6.1 + version: 0.6.1(graphology-types@0.24.7) + graphology-layout-forceatlas2: + specifier: ^0.10.1 + version: 0.10.1(graphology-types@0.24.7) + graphology-layout-noverlap: + specifier: ^0.4.2 + version: 0.4.2(graphology-types@0.24.7) is-promise: specifier: ^4.0.0 version: 4.0.0 @@ -233,6 +245,9 @@ importers: reconnecting-websocket: specifier: ^4.4.0 version: 4.4.0 + sigma: + specifier: 3.0.0-beta.2 + version: 3.0.0-beta.2(graphology-types@0.24.7) tinycolor2: specifier: ^1.4.2 version: 1.4.2 @@ -321,6 +336,9 @@ importers: faker: specifier: ^6.6.6 version: 6.6.6 + graphology-types: + specifier: ^0.24.7 + version: 0.24.7 unplugin-icons: specifier: ^0.17.1 version: 0.17.1 @@ -5455,6 +5473,10 @@ packages: vue: 3.3.4 dev: false + /@yomguithereal/helpers@1.1.1: + resolution: {integrity: sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg==} + dev: false + /abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} @@ -9343,7 +9365,6 @@ packages: /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - dev: true /execa@4.1.0: resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} @@ -10698,6 +10719,55 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /graphology-layout-forceatlas2@0.10.1(graphology-types@0.24.7): + resolution: {integrity: sha512-ogzBeF1FvWzjkikrIFwxhlZXvD2+wlY54lqhsrWprcdPjopM2J9HoMweUmIgwaTvY4bUYVimpSsOdvDv1gPRFQ==} + peerDependencies: + graphology-types: '>=0.19.0' + dependencies: + graphology-types: 0.24.7 + graphology-utils: 2.5.2(graphology-types@0.24.7) + dev: false + + /graphology-layout-noverlap@0.4.2(graphology-types@0.24.7): + resolution: {integrity: sha512-13WwZSx96zim6l1dfZONcqLh3oqyRcjIBsqz2c2iJ3ohgs3605IDWjldH41Gnhh462xGB1j6VGmuGhZ2FKISXA==} + peerDependencies: + graphology-types: '>=0.19.0' + dependencies: + graphology-types: 0.24.7 + graphology-utils: 2.5.2(graphology-types@0.24.7) + dev: false + + /graphology-layout@0.6.1(graphology-types@0.24.7): + resolution: {integrity: sha512-m9aMvbd0uDPffUCFPng5ibRkb2pmfNvdKjQWeZrf71RS1aOoat5874+DcyNfMeCT4aQguKC7Lj9eCbqZj/h8Ag==} + peerDependencies: + graphology-types: '>=0.19.0' + dependencies: + graphology-types: 0.24.7 + graphology-utils: 2.5.2(graphology-types@0.24.7) + pandemonium: 2.4.1 + dev: false + + /graphology-types@0.24.7: + resolution: {integrity: sha512-tdcqOOpwArNjEr0gNQKCXwaNCWnQJrog14nJNQPeemcLnXQUUGrsCWpWkVKt46zLjcS6/KGoayeJfHHyPDlvwA==} + + /graphology-utils@2.5.2(graphology-types@0.24.7): + resolution: {integrity: sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==} + peerDependencies: + graphology-types: '>=0.23.0' + dependencies: + graphology-types: 0.24.7 + dev: false + + /graphology@0.25.4(graphology-types@0.24.7): + resolution: {integrity: sha512-33g0Ol9nkWdD6ulw687viS8YJQBxqG5LWII6FI6nul0pq6iM2t5EKquOTFDbyTblRB3O9I+7KX4xI8u5ffekAQ==} + peerDependencies: + graphology-types: '>=0.24.0' + dependencies: + events: 3.3.0 + graphology-types: 0.24.7 + obliterator: 2.0.4 + dev: false + /graphql@16.5.0: resolution: {integrity: sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -13941,6 +14011,12 @@ packages: ufo: 1.3.1 dev: true + /mnemonist@0.39.6: + resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==} + dependencies: + obliterator: 2.0.4 + dev: false + /module-definition@4.1.0: resolution: {integrity: sha512-rHXi/DpMcD2qcKbPCTklDbX9lBKJrUSl971TW5l6nMpqKCIlzJqmQ8cfEF5M923h2OOLHPDVlh5pJxNyV+AJlw==} engines: {node: '>=12'} @@ -14673,6 +14749,10 @@ packages: es-abstract: 1.20.4 dev: true + /obliterator@2.0.4: + resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} + dev: false + /omit.js@2.0.2: resolution: {integrity: sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg==} dev: true @@ -15060,6 +15140,12 @@ packages: - supports-color dev: true + /pandemonium@2.4.1: + resolution: {integrity: sha512-wRqjisUyiUfXowgm7MFH2rwJzKIr20rca5FsHXCMNm1W5YPP1hCtrZfgmQ62kP7OZ7Xt+cR858aB28lu5NX55g==} + dependencies: + mnemonist: 0.39.6 + dev: false + /parallel-transform@1.2.0: resolution: {integrity: sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==} dependencies: @@ -16809,6 +16895,16 @@ packages: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} dev: true + /sigma@3.0.0-beta.2(graphology-types@0.24.7): + resolution: {integrity: sha512-MfQTEJGsykg/Ont4ZhExeFd/qrInjb3dR0NLvQCncxxR0vW8ZFm06bKoVt69Ss4h5HX8b+50rOglyL6ShRND4w==} + dependencies: + '@yomguithereal/helpers': 1.1.1 + events: 3.3.0 + graphology-utils: 2.5.2(graphology-types@0.24.7) + transitivePeerDependencies: + - graphology-types + dev: false + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}