@statelyai/graph 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,27 @@
1
1
  //#region src/formats/xyflow/index.ts
2
+ const STATELYAI_METADATA_KEY = "__statelyai";
3
+ function toDataObject(value) {
4
+ return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
5
+ }
6
+ function withMetadata(value, metadata) {
7
+ return {
8
+ ...toDataObject(value),
9
+ [STATELYAI_METADATA_KEY]: {
10
+ ...value !== void 0 && { data: value },
11
+ ...metadata
12
+ }
13
+ };
14
+ }
15
+ function readMetadata(value) {
16
+ if (!value || typeof value !== "object") return void 0;
17
+ const metadata = value[STATELYAI_METADATA_KEY];
18
+ return metadata && typeof metadata === "object" ? metadata : void 0;
19
+ }
20
+ function readUserData(value) {
21
+ const metadata = readMetadata(value);
22
+ if (metadata && "data" in metadata) return metadata.data;
23
+ return value;
24
+ }
2
25
  /**
3
26
  * Converts a visual graph to xyflow (React Flow / Svelte Flow) format.
4
27
  *
@@ -21,6 +44,14 @@
21
44
  */
22
45
  function toXYFlow(graph) {
23
46
  return {
47
+ data: { [STATELYAI_METADATA_KEY]: { graph: {
48
+ id: graph.id,
49
+ type: graph.type,
50
+ initialNodeId: graph.initialNodeId,
51
+ data: graph.data,
52
+ direction: graph.direction,
53
+ style: graph.style
54
+ } } },
24
55
  nodes: graph.nodes.map((n) => {
25
56
  const node = {
26
57
  id: n.id,
@@ -28,12 +59,18 @@ function toXYFlow(graph) {
28
59
  x: n.x,
29
60
  y: n.y
30
61
  },
31
- data: n.data ?? {}
62
+ data: withMetadata(n.data, { node: {
63
+ initialNodeId: n.initialNodeId,
64
+ label: n.label,
65
+ color: n.color,
66
+ style: n.style,
67
+ ports: n.ports
68
+ } })
32
69
  };
33
70
  if (n.parentId) node.parentId = n.parentId;
34
71
  if (n.shape) node.type = n.shape;
35
- if (n.width) node.width = n.width;
36
- if (n.height) node.height = n.height;
72
+ if (n.width !== void 0) node.width = n.width;
73
+ if (n.height !== void 0) node.height = n.height;
37
74
  return node;
38
75
  }),
39
76
  edges: graph.edges.map((e) => {
@@ -44,11 +81,17 @@ function toXYFlow(graph) {
44
81
  };
45
82
  if (e.sourcePort) edge.sourceHandle = e.sourcePort;
46
83
  if (e.targetPort) edge.targetHandle = e.targetPort;
47
- if (e.data !== void 0) edge.data = e.data;
48
- if (e.label) edge.data = {
49
- ...edge.data,
50
- label: e.label
51
- };
84
+ edge.data = withMetadata(e.data, { edge: {
85
+ label: e.label,
86
+ weight: e.weight,
87
+ color: e.color,
88
+ style: e.style,
89
+ x: e.x,
90
+ y: e.y,
91
+ width: e.width,
92
+ height: e.height
93
+ } });
94
+ if (e.label) edge.data.label = e.label;
52
95
  return edge;
53
96
  })
54
97
  };
@@ -73,39 +116,53 @@ function fromXYFlow(flow) {
73
116
  if (!flow || typeof flow !== "object") throw new Error("XYFlow: expected an object");
74
117
  if (!Array.isArray(flow.nodes)) throw new Error("XYFlow: \"nodes\" must be an array");
75
118
  if (!Array.isArray(flow.edges)) throw new Error("XYFlow: \"edges\" must be an array");
119
+ const graphMetadata = readMetadata(flow.data)?.graph;
76
120
  return {
77
- id: "",
78
- type: "directed",
79
- initialNodeId: null,
80
- data: void 0,
81
- direction: "down",
82
- nodes: flow.nodes.map((n) => ({
83
- type: "node",
84
- id: n.id,
85
- parentId: n.parentId ?? null,
86
- initialNodeId: null,
87
- label: "",
88
- data: n.data,
89
- x: n.position.x,
90
- y: n.position.y,
91
- width: n.measured?.width ?? n.width ?? n.initialWidth ?? 0,
92
- height: n.measured?.height ?? n.height ?? n.initialHeight ?? 0,
93
- ...n.type && { shape: n.type }
94
- })),
95
- edges: flow.edges.map((e, i) => ({
96
- type: "edge",
97
- id: e.id ?? `e${i}`,
98
- sourceId: e.source,
99
- targetId: e.target,
100
- label: e.data?.label?.toString() ?? "",
101
- ...e.sourceHandle && { sourcePort: e.sourceHandle },
102
- ...e.targetHandle && { targetPort: e.targetHandle },
103
- data: e.data,
104
- x: 0,
105
- y: 0,
106
- width: 0,
107
- height: 0
108
- }))
121
+ id: graphMetadata?.id?.toString() ?? "",
122
+ type: graphMetadata?.type === "undirected" ? "undirected" : "directed",
123
+ initialNodeId: graphMetadata && "initialNodeId" in graphMetadata ? graphMetadata.initialNodeId : null,
124
+ data: graphMetadata && "data" in graphMetadata ? graphMetadata.data : void 0,
125
+ direction: graphMetadata?.direction ?? "down",
126
+ ...graphMetadata?.style !== void 0 && { style: graphMetadata.style },
127
+ nodes: flow.nodes.map((n) => {
128
+ const metadata = readMetadata(n.data)?.node;
129
+ return {
130
+ type: "node",
131
+ id: n.id,
132
+ parentId: n.parentId ?? null,
133
+ initialNodeId: metadata && "initialNodeId" in metadata ? metadata.initialNodeId : null,
134
+ label: metadata && "label" in metadata ? metadata.label : "",
135
+ data: readUserData(n.data),
136
+ x: n.position.x,
137
+ y: n.position.y,
138
+ width: n.measured?.width ?? n.width ?? n.initialWidth ?? 0,
139
+ height: n.measured?.height ?? n.height ?? n.initialHeight ?? 0,
140
+ ...n.type && { shape: n.type },
141
+ ...metadata?.color !== void 0 && { color: metadata.color },
142
+ ...metadata?.style !== void 0 && { style: metadata.style },
143
+ ...metadata?.ports !== void 0 && { ports: metadata.ports }
144
+ };
145
+ }),
146
+ edges: flow.edges.map((e, i) => {
147
+ const metadata = readMetadata(e.data)?.edge;
148
+ return {
149
+ type: "edge",
150
+ id: e.id ?? `e${i}`,
151
+ sourceId: e.source,
152
+ targetId: e.target,
153
+ label: metadata && "label" in metadata ? metadata.label : e.data?.label?.toString() ?? "",
154
+ ...e.sourceHandle && { sourcePort: e.sourceHandle },
155
+ ...e.targetHandle && { targetPort: e.targetHandle },
156
+ data: readUserData(e.data),
157
+ x: metadata?.x ?? 0,
158
+ y: metadata?.y ?? 0,
159
+ width: metadata?.width ?? 0,
160
+ height: metadata?.height ?? 0,
161
+ ...metadata?.weight !== void 0 && { weight: metadata.weight },
162
+ ...metadata?.color !== void 0 && { color: metadata.color },
163
+ ...metadata?.style !== void 0 && { style: metadata.style }
164
+ };
165
+ })
109
166
  };
110
167
  }
111
168
  /**
package/dist/index.d.mts CHANGED
@@ -11,7 +11,7 @@ import { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdge
11
11
  * @example
12
12
  * ```ts
13
13
  * const port = createGraphPort({ name: 'output', direction: 'out' });
14
- * // { name: 'output', direction: 'out', data: undefined }
14
+ * // { name: 'output', direction: 'out', data: null }
15
15
  * ```
16
16
  */
17
17
  declare function createGraphPort<P = any>(config: PortConfig<P>): GraphPort<P>;
@@ -31,7 +31,7 @@ declare function createGraphNode<N = any, P = any>(config: NodeConfig<N, P>): Gr
31
31
  * @example
32
32
  * ```ts
33
33
  * const edge = createGraphEdge({ id: 'e1', sourceId: 'a', targetId: 'b' });
34
- * // { type: 'edge', id: 'e1', sourceId: 'a', targetId: 'b', label: null, data: undefined }
34
+ * // { type: 'edge', id: 'e1', sourceId: 'a', targetId: 'b', label: null, data: null }
35
35
  * ```
36
36
  */
37
37
  declare function createGraphEdge<T = any>(config: EdgeConfig<T>): GraphEdge<T>;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { o as invalidateIndex, t as getIndex } from "./indexing-CJc-ul8e.mjs";
2
- import { $ as createGraphPort, A as isAcyclic, B as getShortestPaths, C as getPreorder, D as getConnectedComponents, E as dfs, F as genSimplePaths, G as GraphInstance, H as getSimplePaths, I as getAStarPath, J as addNode, K as addEdge, L as getAllPairsShortestPaths, M as isTree, N as genCycles, O as getTopologicalSort, P as genShortestPaths, Q as createGraphNode, R as getCycles, S as getPostorders, T as bfs, U as getStronglyConnectedComponents, V as getSimplePath, W as joinPaths, X as createGraphEdge, Y as createGraph, Z as createGraphFromTransition, _ as getPageRank, a as genGirvanNewmanCommunities, at as getNode, b as genPreorders, c as getLabelPropagationCommunities, ct as updateEdge, d as getClosenessCentrality, et as createVisualGraph, f as getDegreeCentrality, g as getOutDegreeCentrality, h as getInDegreeCentrality, i as getBridges, it as getEdge, j as isConnected, k as hasPath, l as getModularity, lt as updateEntities, m as getHITS, n as getArticulationPoints, nt as deleteEntities, o as getGirvanNewmanCommunities, ot as hasEdge, p as getEigenvectorCentrality, q as addEntities, r as getBiconnectedComponents, rt as deleteNode, s as getGreedyModularityCommunities, st as hasNode, t as isIsomorphic, tt as deleteEdge, u as getBetweennessCentrality, ut as updateNode, v as getMinimumSpanningTree, w as getPreorders, x as getPostorder, y as genPostorders, z as getShortestPath } from "./algorithms-BHHg7lGq.mjs";
1
+ import { o as invalidateIndex, t as getIndex } from "./indexing-DUl3kTqm.mjs";
2
+ import { $ as createGraphPort, A as isAcyclic, B as getShortestPaths, C as getPreorder, D as getConnectedComponents, E as dfs, F as genSimplePaths, G as GraphInstance, H as getSimplePaths, I as getAStarPath, J as addNode, K as addEdge, L as getAllPairsShortestPaths, M as isTree, N as genCycles, O as getTopologicalSort, P as genShortestPaths, Q as createGraphNode, R as getCycles, S as getPostorders, T as bfs, U as getStronglyConnectedComponents, V as getSimplePath, W as joinPaths, X as createGraphEdge, Y as createGraph, Z as createGraphFromTransition, _ as getPageRank, a as genGirvanNewmanCommunities, at as getNode, b as genPreorders, c as getLabelPropagationCommunities, ct as updateEdge, d as getClosenessCentrality, et as createVisualGraph, f as getDegreeCentrality, g as getOutDegreeCentrality, h as getInDegreeCentrality, i as getBridges, it as getEdge, j as isConnected, k as hasPath, l as getModularity, lt as updateEntities, m as getHITS, n as getArticulationPoints, nt as deleteEntities, o as getGirvanNewmanCommunities, ot as hasEdge, p as getEigenvectorCentrality, q as addEntities, r as getBiconnectedComponents, rt as deleteNode, s as getGreedyModularityCommunities, st as hasNode, t as isIsomorphic, tt as deleteEdge, u as getBetweennessCentrality, ut as updateNode, v as getMinimumSpanningTree, w as getPreorders, x as getPostorder, y as genPostorders, z as getShortestPath } from "./algorithms-BNDQcHU3.mjs";
3
3
  import { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgesBetween, getEdgesByPort, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPort, getPorts, getPredecessors, getRelativeDistance, getRelativeDistanceMap, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf } from "./queries.mjs";
4
4
  import { n as createFormatConverter } from "./converter-Dspillnn.mjs";
5
5
 
@@ -20,8 +20,10 @@ const indexes = /* @__PURE__ */ new WeakMap();
20
20
  */
21
21
  function getIndex(graph) {
22
22
  let idx = indexes.get(graph);
23
- if (!idx || idx.nodeCount !== graph.nodes.length || idx.edgeCount !== graph.edges.length) {
24
- idx = buildIndex(graph);
23
+ const sameStructure = idx && idx.nodeCount === graph.nodes.length && idx.edgeCount === graph.edges.length;
24
+ const signature = idx !== void 0 && sameStructure && idx.nodesRef === graph.nodes && idx.edgesRef === graph.edges ? getIndexSignature(graph) : void 0;
25
+ if (!idx || idx.nodeCount !== graph.nodes.length || idx.edgeCount !== graph.edges.length || signature !== void 0 && idx.signature !== signature) {
26
+ idx = buildIndex(graph, signature ?? getIndexSignature(graph));
25
27
  indexes.set(graph, idx);
26
28
  }
27
29
  return idx;
@@ -42,7 +44,12 @@ function getIndex(graph) {
42
44
  function invalidateIndex(graph) {
43
45
  indexes.delete(graph);
44
46
  }
45
- function buildIndex(graph) {
47
+ function getIndexSignature(graph) {
48
+ const nodeParts = graph.nodes.map((node) => `${node.id}\u0000${node.parentId ?? ""}`);
49
+ const edgeParts = graph.edges.map((edge) => `${edge.id}\u0000${edge.sourceId}\u0000${edge.targetId}`);
50
+ return `${nodeParts.join("")}\u0002${edgeParts.join("")}`;
51
+ }
52
+ function buildIndex(graph, signature) {
46
53
  const nodeById = /* @__PURE__ */ new Map();
47
54
  const edgeById = /* @__PURE__ */ new Map();
48
55
  const outEdges = /* @__PURE__ */ new Map();
@@ -70,7 +77,10 @@ function buildIndex(graph) {
70
77
  inEdges,
71
78
  childNodes,
72
79
  nodeCount: graph.nodes.length,
73
- edgeCount: graph.edges.length
80
+ edgeCount: graph.edges.length,
81
+ signature,
82
+ nodesRef: graph.nodes,
83
+ edgesRef: graph.edges
74
84
  };
75
85
  }
76
86
  function indexAddNode(idx, node, arrayIndex) {
@@ -81,12 +91,14 @@ function indexAddNode(idx, node, arrayIndex) {
81
91
  if (!idx.childNodes.has(parent)) idx.childNodes.set(parent, []);
82
92
  idx.childNodes.get(parent).push(node.id);
83
93
  idx.nodeCount++;
94
+ idx.signature = "";
84
95
  }
85
96
  function indexAddEdge(idx, edge, arrayIndex) {
86
97
  idx.edgeById.set(edge.id, arrayIndex);
87
98
  idx.outEdges.get(edge.sourceId)?.push(edge.id);
88
99
  idx.inEdges.get(edge.targetId)?.push(edge.id);
89
100
  idx.edgeCount++;
101
+ idx.signature = "";
90
102
  }
91
103
  /** Update childNodes index when a node's parentId changes. */
92
104
  function indexReparentNode(idx, nodeId, oldParentId, newParentId) {
@@ -98,6 +110,7 @@ function indexReparentNode(idx, nodeId, oldParentId, newParentId) {
98
110
  const np = newParentId ?? null;
99
111
  if (!idx.childNodes.has(np)) idx.childNodes.set(np, []);
100
112
  idx.childNodes.get(np).push(nodeId);
113
+ idx.signature = "";
101
114
  }
102
115
  /** Update adjacency lists when an edge's sourceId/targetId changes. */
103
116
  function indexUpdateEdgeEndpoints(idx, edgeId, oldSourceId, oldTargetId, newSourceId, newTargetId) {
@@ -117,6 +130,7 @@ function indexUpdateEdgeEndpoints(idx, edgeId, oldSourceId, oldTargetId, newSour
117
130
  }
118
131
  idx.inEdges.get(newTargetId)?.push(edgeId);
119
132
  }
133
+ idx.signature = "";
120
134
  }
121
135
 
122
136
  //#endregion
package/dist/queries.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as getIndex } from "./indexing-CJc-ul8e.mjs";
1
+ import { t as getIndex } from "./indexing-DUl3kTqm.mjs";
2
2
 
3
3
  //#region src/queries.ts
4
4
  /**
@@ -4,11 +4,11 @@ import * as z from "zod";
4
4
  //#region src/schemas.d.ts
5
5
  declare const PortSchema: z.ZodObject<{
6
6
  name: z.ZodString;
7
- direction: z.ZodOptional<z.ZodEnum<{
7
+ direction: z.ZodEnum<{
8
8
  in: "in";
9
9
  out: "out";
10
10
  inout: "inout";
11
- }>>;
11
+ }>;
12
12
  label: z.ZodOptional<z.ZodString>;
13
13
  data: z.ZodAny;
14
14
  x: z.ZodOptional<z.ZodNumber>;
@@ -33,11 +33,11 @@ declare const NodeSchema: z.ZodObject<{
33
33
  style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
34
34
  ports: z.ZodOptional<z.ZodArray<z.ZodObject<{
35
35
  name: z.ZodString;
36
- direction: z.ZodOptional<z.ZodEnum<{
36
+ direction: z.ZodEnum<{
37
37
  in: "in";
38
38
  out: "out";
39
39
  inout: "inout";
40
- }>>;
40
+ }>;
41
41
  label: z.ZodOptional<z.ZodString>;
42
42
  data: z.ZodAny;
43
43
  x: z.ZodOptional<z.ZodNumber>;
@@ -87,11 +87,11 @@ declare const GraphSchema: z.ZodObject<{
87
87
  style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
88
88
  ports: z.ZodOptional<z.ZodArray<z.ZodObject<{
89
89
  name: z.ZodString;
90
- direction: z.ZodOptional<z.ZodEnum<{
90
+ direction: z.ZodEnum<{
91
91
  in: "in";
92
92
  out: "out";
93
93
  inout: "inout";
94
- }>>;
94
+ }>;
95
95
  label: z.ZodOptional<z.ZodString>;
96
96
  data: z.ZodAny;
97
97
  x: z.ZodOptional<z.ZodNumber>;
@@ -140,5 +140,6 @@ declare function getGraphPortIssues(value: unknown): GraphValidationIssue[];
140
140
  declare function getGraphNodeIssues(value: unknown): GraphValidationIssue[];
141
141
  declare function getGraphEdgeIssues(value: unknown): GraphValidationIssue[];
142
142
  declare function getGraphIssues(value: unknown): GraphValidationIssue[];
143
+ declare function validateGraph(value: unknown): GraphValidationIssue[];
143
144
  //#endregion
144
- export { EdgeSchema, GraphSchema, GraphValidationIssue, NodeSchema, PortSchema, getGraphEdgeIssues, getGraphIssues, getGraphNodeIssues, getGraphPortIssues, isGraph, isGraphEdge, isGraphNode, isGraphPort };
145
+ export { EdgeSchema, GraphSchema, GraphValidationIssue, NodeSchema, PortSchema, getGraphEdgeIssues, getGraphIssues, getGraphNodeIssues, getGraphPortIssues, isGraph, isGraphEdge, isGraphNode, isGraphPort, validateGraph };
package/dist/schemas.mjs CHANGED
@@ -9,7 +9,7 @@ const PortDirectionSchema = z.enum([
9
9
  ]);
10
10
  const PortSchema = z.object({
11
11
  name: z.string(),
12
- direction: PortDirectionSchema.optional(),
12
+ direction: PortDirectionSchema,
13
13
  label: z.string().optional(),
14
14
  data: z.any(),
15
15
  x: z.number().optional(),
@@ -85,7 +85,7 @@ function isGraphEdge(value) {
85
85
  return EdgeSchema.safeParse(value).success;
86
86
  }
87
87
  function isGraph(value) {
88
- return GraphSchema.safeParse(value).success;
88
+ return validateGraph(value).length === 0;
89
89
  }
90
90
  function getGraphPortIssues(value) {
91
91
  return getValidationIssues(PortSchema, value);
@@ -97,8 +97,131 @@ function getGraphEdgeIssues(value) {
97
97
  return getValidationIssues(EdgeSchema, value);
98
98
  }
99
99
  function getGraphIssues(value) {
100
- return getValidationIssues(GraphSchema, value);
100
+ return validateGraph(value);
101
+ }
102
+ function createIssue(code, message, path) {
103
+ return {
104
+ code,
105
+ message,
106
+ path
107
+ };
108
+ }
109
+ function getDuplicateIndexes(items, getKey) {
110
+ const indexesByKey = /* @__PURE__ */ new Map();
111
+ items.forEach((item, index) => {
112
+ const key = getKey(item);
113
+ if (key == null) return;
114
+ const indexes = indexesByKey.get(key) ?? [];
115
+ indexes.push(index);
116
+ indexesByKey.set(key, indexes);
117
+ });
118
+ for (const [key, indexes] of indexesByKey) if (indexes.length < 2) indexesByKey.delete(key);
119
+ return indexesByKey;
120
+ }
121
+ function getGraphInvariantIssues(graph) {
122
+ const issues = [];
123
+ const nodeIndexes = /* @__PURE__ */ new Map();
124
+ const nodesById = /* @__PURE__ */ new Map();
125
+ for (const [id, indexes] of getDuplicateIndexes(graph.nodes, (node) => node.id)) for (const index of indexes) issues.push(createIssue("duplicate_node_id", `Duplicate node id "${id}"`, [
126
+ "nodes",
127
+ index,
128
+ "id"
129
+ ]));
130
+ for (const [id, indexes] of getDuplicateIndexes(graph.edges, (edge) => edge.id)) for (const index of indexes) issues.push(createIssue("duplicate_edge_id", `Duplicate edge id "${id}"`, [
131
+ "edges",
132
+ index,
133
+ "id"
134
+ ]));
135
+ graph.nodes.forEach((node, index) => {
136
+ nodeIndexes.set(node.id, index);
137
+ nodesById.set(node.id, node);
138
+ });
139
+ if (graph.initialNodeId && !nodeIndexes.has(graph.initialNodeId)) issues.push(createIssue("missing_initial_node", `Initial node "${graph.initialNodeId}" does not exist`, ["initialNodeId"]));
140
+ graph.nodes.forEach((node, index) => {
141
+ if (node.id === "") issues.push(createIssue("empty_node_id", "Node id must be a non-empty string", [
142
+ "nodes",
143
+ index,
144
+ "id"
145
+ ]));
146
+ if (node.parentId === "") issues.push(createIssue("empty_parent_id", "Node parentId must be a non-empty string", [
147
+ "nodes",
148
+ index,
149
+ "parentId"
150
+ ]));
151
+ else if (node.parentId != null && !nodeIndexes.has(node.parentId)) issues.push(createIssue("missing_parent", `Parent node "${node.parentId}" does not exist`, [
152
+ "nodes",
153
+ index,
154
+ "parentId"
155
+ ]));
156
+ if (node.initialNodeId && !nodeIndexes.has(node.initialNodeId)) issues.push(createIssue("missing_node_initial", `Initial node "${node.initialNodeId}" does not exist`, [
157
+ "nodes",
158
+ index,
159
+ "initialNodeId"
160
+ ]));
161
+ for (const [name, indexes] of getDuplicateIndexes(node.ports ?? [], (port) => port.name)) for (const portIndex of indexes) issues.push(createIssue("duplicate_port_name", `Duplicate port name "${name}" on node "${node.id}"`, [
162
+ "nodes",
163
+ index,
164
+ "ports",
165
+ portIndex,
166
+ "name"
167
+ ]));
168
+ });
169
+ for (const node of graph.nodes) {
170
+ const seen = /* @__PURE__ */ new Set();
171
+ let current = node.parentId;
172
+ while (current != null) {
173
+ if (current === node.id || seen.has(current)) {
174
+ issues.push(createIssue("parent_cycle", `Node "${node.id}" is part of a parent cycle`, [
175
+ "nodes",
176
+ nodeIndexes.get(node.id) ?? 0,
177
+ "parentId"
178
+ ]));
179
+ break;
180
+ }
181
+ seen.add(current);
182
+ current = nodesById.get(current)?.parentId;
183
+ }
184
+ }
185
+ graph.edges.forEach((edge, index) => {
186
+ if (edge.id === "") issues.push(createIssue("empty_edge_id", "Edge id must be a non-empty string", [
187
+ "edges",
188
+ index,
189
+ "id"
190
+ ]));
191
+ const source = nodesById.get(edge.sourceId);
192
+ const target = nodesById.get(edge.targetId);
193
+ if (!source) issues.push(createIssue("missing_source_node", `Source node "${edge.sourceId}" does not exist`, [
194
+ "edges",
195
+ index,
196
+ "sourceId"
197
+ ]));
198
+ if (!target) issues.push(createIssue("missing_target_node", `Target node "${edge.targetId}" does not exist`, [
199
+ "edges",
200
+ index,
201
+ "targetId"
202
+ ]));
203
+ if (source && edge.sourcePort !== void 0 && !source.ports?.some((port) => port.name === edge.sourcePort)) issues.push(createIssue("missing_source_port", `Port "${edge.sourcePort}" does not exist on source node "${edge.sourceId}"`, [
204
+ "edges",
205
+ index,
206
+ "sourcePort"
207
+ ]));
208
+ if (target && edge.targetPort !== void 0 && !target.ports?.some((port) => port.name === edge.targetPort)) issues.push(createIssue("missing_target_port", `Port "${edge.targetPort}" does not exist on target node "${edge.targetId}"`, [
209
+ "edges",
210
+ index,
211
+ "targetPort"
212
+ ]));
213
+ });
214
+ return issues;
215
+ }
216
+ function validateGraph(value) {
217
+ const shapeResult = GraphSchema.safeParse(value);
218
+ if (!shapeResult.success) return shapeResult.error.issues.map((issue) => ({
219
+ code: issue.code,
220
+ message: issue.message,
221
+ path: issue.path.map((segment) => typeof segment === "symbol" ? String(segment) : segment)
222
+ }));
223
+ return getGraphInvariantIssues(shapeResult.data);
101
224
  }
102
225
 
103
226
  //#endregion
104
- export { EdgeSchema, GraphSchema, NodeSchema, PortSchema, getGraphEdgeIssues, getGraphIssues, getGraphNodeIssues, getGraphPortIssues, isGraph, isGraphEdge, isGraphNode, isGraphPort };
227
+ export { EdgeSchema, GraphSchema, NodeSchema, PortSchema, getGraphEdgeIssues, getGraphIssues, getGraphNodeIssues, getGraphPortIssues, isGraph, isGraphEdge, isGraphNode, isGraphPort, validateGraph };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@statelyai/graph",
3
3
  "type": "module",
4
- "version": "0.12.0",
4
+ "version": "0.13.0",
5
5
  "description": "A TypeScript-first graph library with plain JSON-serializable objects",
6
6
  "author": "David Khourshid <david@stately.ai>",
7
7
  "license": "MIT",
@@ -150,6 +150,7 @@
150
150
  },
151
151
  "required": [
152
152
  "name",
153
+ "direction",
153
154
  "data"
154
155
  ],
155
156
  "additionalProperties": false
@@ -125,6 +125,7 @@
125
125
  },
126
126
  "required": [
127
127
  "name",
128
+ "direction",
128
129
  "data"
129
130
  ],
130
131
  "additionalProperties": false