@statelyai/graph 0.13.0 → 2.0.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.
Files changed (57) hide show
  1. package/README.md +57 -26
  2. package/dist/{adjacency-list-Ca0VjKIf.mjs → adjacency-list-GeL1Cu-L.mjs} +5 -3
  3. package/dist/{algorithms-BlM-qoJb.d.mts → algorithms-CsGNehct.d.mts} +137 -2
  4. package/dist/{algorithms-BNDQcHU3.mjs → algorithms-DF1pSQGv.mjs} +1494 -357
  5. package/dist/algorithms.d.mts +2 -2
  6. package/dist/algorithms.mjs +2 -2
  7. package/dist/{converter-Dspillnn.mjs → converter-DyCJJfTe.mjs} +2 -2
  8. package/dist/{edge-list-gKe8-iRa.mjs → edge-list-BcZ0h6zz.mjs} +1 -1
  9. package/dist/format-support.mjs +67 -11
  10. package/dist/formats/adjacency-list/index.d.mts +1 -1
  11. package/dist/formats/adjacency-list/index.mjs +1 -1
  12. package/dist/formats/converter/index.d.mts +1 -60
  13. package/dist/formats/converter/index.mjs +1 -1
  14. package/dist/formats/cytoscape/index.d.mts +1 -1
  15. package/dist/formats/cytoscape/index.mjs +5 -3
  16. package/dist/formats/d2/index.d.mts +109 -0
  17. package/dist/formats/d2/index.mjs +1100 -0
  18. package/dist/formats/d3/index.d.mts +2 -2
  19. package/dist/formats/d3/index.mjs +5 -3
  20. package/dist/formats/dot/index.d.mts +1 -1
  21. package/dist/formats/dot/index.mjs +24 -8
  22. package/dist/formats/edge-list/index.d.mts +1 -1
  23. package/dist/formats/edge-list/index.mjs +1 -1
  24. package/dist/formats/elk/index.d.mts +1 -1
  25. package/dist/formats/elk/index.mjs +23 -16
  26. package/dist/formats/gexf/index.d.mts +1 -1
  27. package/dist/formats/gexf/index.mjs +30 -17
  28. package/dist/formats/gml/index.d.mts +1 -1
  29. package/dist/formats/gml/index.mjs +22 -13
  30. package/dist/formats/graphml/index.d.mts +1 -1
  31. package/dist/formats/graphml/index.mjs +83 -25
  32. package/dist/formats/jgf/index.d.mts +1 -1
  33. package/dist/formats/jgf/index.mjs +6 -3
  34. package/dist/formats/mermaid/index.d.mts +1 -1
  35. package/dist/formats/mermaid/index.mjs +57 -20
  36. package/dist/formats/tgf/index.d.mts +1 -1
  37. package/dist/formats/tgf/index.mjs +2 -2
  38. package/dist/formats/xyflow/index.d.mts +1 -1
  39. package/dist/formats/xyflow/index.mjs +33 -6
  40. package/dist/index-D51lJnt2.d.mts +61 -0
  41. package/dist/index-DWmo1mIp.d.mts +697 -0
  42. package/dist/index.d.mts +6 -631
  43. package/dist/index.mjs +144 -295
  44. package/dist/mode-D8OnHFBk.mjs +15 -0
  45. package/dist/queries-BfXeTXRf.d.mts +547 -0
  46. package/dist/queries-KirMDR7e.mjs +980 -0
  47. package/dist/queries.d.mts +1 -514
  48. package/dist/queries.mjs +1 -766
  49. package/dist/schemas.d.mts +21 -10
  50. package/dist/schemas.mjs +35 -86
  51. package/dist/{types-CnZ01raw.d.mts → types-DNYdIU21.d.mts} +83 -11
  52. package/dist/validate-TtH-x3JV.mjs +190 -0
  53. package/package.json +14 -3
  54. package/schemas/edge.schema.json +11 -0
  55. package/schemas/graph.schema.json +24 -3
  56. package/schemas/node.schema.json +6 -0
  57. package/dist/indexing-DUl3kTqm.mjs +0 -137
@@ -1,4 +1,4 @@
1
- import { g as GraphNode, p as GraphEdge, u as Graph, y as GraphPort } from "./types-CnZ01raw.mjs";
1
+ import { d as Graph, m as GraphEdge, v as GraphNode, x as GraphPort } from "./types-DNYdIU21.mjs";
2
2
  import * as z from "zod";
3
3
 
4
4
  //#region src/schemas.d.ts
@@ -15,7 +15,7 @@ declare const PortSchema: z.ZodObject<{
15
15
  y: z.ZodOptional<z.ZodNumber>;
16
16
  width: z.ZodOptional<z.ZodNumber>;
17
17
  height: z.ZodOptional<z.ZodNumber>;
18
- style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
18
+ style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
19
19
  }, z.core.$strip>;
20
20
  declare const NodeSchema: z.ZodObject<{
21
21
  type: z.ZodLiteral<"node">;
@@ -30,7 +30,7 @@ declare const NodeSchema: z.ZodObject<{
30
30
  height: z.ZodOptional<z.ZodNumber>;
31
31
  shape: z.ZodOptional<z.ZodString>;
32
32
  color: z.ZodOptional<z.ZodString>;
33
- style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
33
+ style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
34
34
  ports: z.ZodOptional<z.ZodArray<z.ZodObject<{
35
35
  name: z.ZodString;
36
36
  direction: z.ZodEnum<{
@@ -44,7 +44,7 @@ declare const NodeSchema: z.ZodObject<{
44
44
  y: z.ZodOptional<z.ZodNumber>;
45
45
  width: z.ZodOptional<z.ZodNumber>;
46
46
  height: z.ZodOptional<z.ZodNumber>;
47
- style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
47
+ style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
48
48
  }, z.core.$strip>>>;
49
49
  }, z.core.$strip>;
50
50
  declare const EdgeSchema: z.ZodObject<{
@@ -56,19 +56,25 @@ declare const EdgeSchema: z.ZodObject<{
56
56
  weight: z.ZodOptional<z.ZodNumber>;
57
57
  sourcePort: z.ZodOptional<z.ZodString>;
58
58
  targetPort: z.ZodOptional<z.ZodString>;
59
+ mode: z.ZodOptional<z.ZodEnum<{
60
+ directed: "directed";
61
+ undirected: "undirected";
62
+ bidirectional: "bidirectional";
63
+ }>>;
59
64
  data: z.ZodAny;
60
65
  x: z.ZodOptional<z.ZodNumber>;
61
66
  y: z.ZodOptional<z.ZodNumber>;
62
67
  width: z.ZodOptional<z.ZodNumber>;
63
68
  height: z.ZodOptional<z.ZodNumber>;
64
69
  color: z.ZodOptional<z.ZodString>;
65
- style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
70
+ style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
66
71
  }, z.core.$strip>;
67
72
  declare const GraphSchema: z.ZodObject<{
68
73
  id: z.ZodString;
69
- type: z.ZodEnum<{
74
+ mode: z.ZodEnum<{
70
75
  directed: "directed";
71
76
  undirected: "undirected";
77
+ bidirectional: "bidirectional";
72
78
  }>;
73
79
  initialNodeId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
74
80
  nodes: z.ZodArray<z.ZodObject<{
@@ -84,7 +90,7 @@ declare const GraphSchema: z.ZodObject<{
84
90
  height: z.ZodOptional<z.ZodNumber>;
85
91
  shape: z.ZodOptional<z.ZodString>;
86
92
  color: z.ZodOptional<z.ZodString>;
87
- style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
93
+ style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
88
94
  ports: z.ZodOptional<z.ZodArray<z.ZodObject<{
89
95
  name: z.ZodString;
90
96
  direction: z.ZodEnum<{
@@ -98,7 +104,7 @@ declare const GraphSchema: z.ZodObject<{
98
104
  y: z.ZodOptional<z.ZodNumber>;
99
105
  width: z.ZodOptional<z.ZodNumber>;
100
106
  height: z.ZodOptional<z.ZodNumber>;
101
- style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
107
+ style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
102
108
  }, z.core.$strip>>>;
103
109
  }, z.core.$strip>>;
104
110
  edges: z.ZodArray<z.ZodObject<{
@@ -110,13 +116,18 @@ declare const GraphSchema: z.ZodObject<{
110
116
  weight: z.ZodOptional<z.ZodNumber>;
111
117
  sourcePort: z.ZodOptional<z.ZodString>;
112
118
  targetPort: z.ZodOptional<z.ZodString>;
119
+ mode: z.ZodOptional<z.ZodEnum<{
120
+ directed: "directed";
121
+ undirected: "undirected";
122
+ bidirectional: "bidirectional";
123
+ }>>;
113
124
  data: z.ZodAny;
114
125
  x: z.ZodOptional<z.ZodNumber>;
115
126
  y: z.ZodOptional<z.ZodNumber>;
116
127
  width: z.ZodOptional<z.ZodNumber>;
117
128
  height: z.ZodOptional<z.ZodNumber>;
118
129
  color: z.ZodOptional<z.ZodString>;
119
- style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
130
+ style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
120
131
  }, z.core.$strip>>;
121
132
  data: z.ZodAny;
122
133
  direction: z.ZodOptional<z.ZodEnum<{
@@ -125,7 +136,7 @@ declare const GraphSchema: z.ZodObject<{
125
136
  left: "left";
126
137
  right: "right";
127
138
  }>>;
128
- style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
139
+ style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
129
140
  }, z.core.$strip>;
130
141
  interface GraphValidationIssue {
131
142
  code: string;
package/dist/schemas.mjs CHANGED
@@ -1,7 +1,17 @@
1
+ import { t as getGraphIssues$1 } from "./validate-TtH-x3JV.mjs";
1
2
  import * as z from "zod";
2
3
 
3
4
  //#region src/schemas.ts
4
- const StyleSchema = z.record(z.string(), z.union([z.string(), z.number()]));
5
+ const StyleSchema = z.record(z.string(), z.union([
6
+ z.string(),
7
+ z.number(),
8
+ z.boolean()
9
+ ]));
10
+ const ModeSchema = z.enum([
11
+ "directed",
12
+ "undirected",
13
+ "bidirectional"
14
+ ]);
5
15
  const PortDirectionSchema = z.enum([
6
16
  "in",
7
17
  "out",
@@ -43,6 +53,7 @@ const EdgeSchema = z.object({
43
53
  weight: z.number().optional(),
44
54
  sourcePort: z.string().optional(),
45
55
  targetPort: z.string().optional(),
56
+ mode: ModeSchema.optional(),
46
57
  data: z.any(),
47
58
  x: z.number().optional(),
48
59
  y: z.number().optional(),
@@ -53,7 +64,7 @@ const EdgeSchema = z.object({
53
64
  });
54
65
  const GraphSchema = z.object({
55
66
  id: z.string(),
56
- type: z.enum(["directed", "undirected"]),
67
+ mode: ModeSchema,
57
68
  initialNodeId: z.string().nullable().optional(),
58
69
  nodes: z.array(NodeSchema),
59
70
  edges: z.array(EdgeSchema),
@@ -106,37 +117,30 @@ function createIssue(code, message, path) {
106
117
  path
107
118
  };
108
119
  }
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
+ /** Maps structural issue codes from `validate.ts` to this module's codes. */
121
+ const STRUCTURAL_CODE_MAP = {
122
+ "duplicate-node-id": "duplicate_node_id",
123
+ "duplicate-edge-id": "duplicate_edge_id",
124
+ "missing-initial-node": "missing_initial_node",
125
+ "missing-parent": "missing_parent",
126
+ "missing-node-initial": "missing_node_initial",
127
+ "duplicate-port-name": "duplicate_port_name",
128
+ "parent-cycle": "parent_cycle",
129
+ "missing-source-port": "missing_source_port",
130
+ "missing-target-port": "missing_target_port"
131
+ };
132
+ function toValidationIssue(issue) {
133
+ const path = issue.path ?? [];
134
+ let code = STRUCTURAL_CODE_MAP[issue.code] ?? issue.code;
135
+ if (issue.code === "dangling-edge-endpoint") code = path[path.length - 1] === "sourceId" ? "missing_source_node" : "missing_target_node";
136
+ return {
137
+ code,
138
+ message: issue.message,
139
+ path
140
+ };
120
141
  }
121
142
  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"]));
143
+ const issues = getGraphIssues$1(graph).map(toValidationIssue);
140
144
  graph.nodes.forEach((node, index) => {
141
145
  if (node.id === "") issues.push(createIssue("empty_node_id", "Node id must be a non-empty string", [
142
146
  "nodes",
@@ -148,68 +152,13 @@ function getGraphInvariantIssues(graph) {
148
152
  index,
149
153
  "parentId"
150
154
  ]));
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
155
  });
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
156
  graph.edges.forEach((edge, index) => {
186
157
  if (edge.id === "") issues.push(createIssue("empty_edge_id", "Edge id must be a non-empty string", [
187
158
  "edges",
188
159
  index,
189
160
  "id"
190
161
  ]));
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
162
  });
214
163
  return issues;
215
164
  }
@@ -1,4 +1,15 @@
1
1
  //#region src/types.d.ts
2
+ /**
3
+ * Directedness of a graph or an individual edge.
4
+ *
5
+ * - `'directed'` — edge points from source to target.
6
+ * - `'undirected'` — edge has no direction; traversable both ways.
7
+ * - `'bidirectional'` — edge points both ways (arrows on both ends).
8
+ *
9
+ * Set at the graph level as the default ({@link Graph.mode}); individual edges
10
+ * may override it ({@link GraphEdge.mode}).
11
+ */
12
+ type GraphMode = 'directed' | 'undirected' | 'bidirectional';
2
13
  interface EntityRect {
3
14
  x: number;
4
15
  y: number;
@@ -11,7 +22,7 @@ interface GraphEntity {
11
22
  y?: number;
12
23
  width?: number;
13
24
  height?: number;
14
- style?: Record<string, string | number>;
25
+ style?: Record<string, string | number | boolean>;
15
26
  }
16
27
  /** Visual entity base — required position/size. */
17
28
  interface VisualGraphEntity {
@@ -19,7 +30,7 @@ interface VisualGraphEntity {
19
30
  y: number;
20
31
  width: number;
21
32
  height: number;
22
- style?: Record<string, string | number>;
33
+ style?: Record<string, string | number | boolean>;
23
34
  }
24
35
  type PortDirection = 'in' | 'out' | 'inout';
25
36
  interface PortConfig<TPortData = any> extends GraphEntity {
@@ -42,13 +53,14 @@ interface VisualPort<TPortData = any> extends GraphPort<TPortData> {
42
53
  }
43
54
  interface GraphConfig<TNodeData = any, TEdgeData = any, TGraphData = any, TPortData = any> {
44
55
  id?: string;
45
- type?: 'directed' | 'undirected';
56
+ /** Default directedness for all edges. Defaults to `'directed'`. */
57
+ mode?: GraphMode;
46
58
  initialNodeId?: string;
47
59
  nodes?: NodeConfig<TNodeData, TPortData>[];
48
60
  edges?: EdgeConfig<TEdgeData>[];
49
61
  data?: TGraphData;
50
62
  direction?: 'up' | 'down' | 'left' | 'right';
51
- style?: Record<string, string | number>;
63
+ style?: Record<string, string | number | boolean>;
52
64
  }
53
65
  interface NodeConfig<TNodeData = any, TPortData = any> extends GraphEntity {
54
66
  id: string;
@@ -87,18 +99,24 @@ interface EdgeConfig<TEdgeData = any> extends GraphEntity {
87
99
  sourcePort?: string;
88
100
  /** Port name on the target node this edge connects to. */
89
101
  targetPort?: string;
102
+ /**
103
+ * Per-edge directedness override. When absent, the edge inherits the graph's
104
+ * {@link GraphConfig.mode}.
105
+ */
106
+ mode?: GraphMode;
90
107
  data?: TEdgeData;
91
108
  color?: string;
92
109
  }
93
110
  interface Graph<TNodeData = any, TEdgeData = any, TGraphData = any, TPortData = any> {
94
111
  id: string;
95
- type: 'directed' | 'undirected';
112
+ /** Default directedness for all edges. */
113
+ mode: GraphMode;
96
114
  initialNodeId?: string | null;
97
115
  nodes: GraphNode<TNodeData, TPortData>[];
98
116
  edges: GraphEdge<TEdgeData>[];
99
117
  data: TGraphData;
100
118
  direction?: 'up' | 'down' | 'left' | 'right';
101
- style?: Record<string, string | number>;
119
+ style?: Record<string, string | number | boolean>;
102
120
  }
103
121
  interface GraphNode<TNodeData = any, TPortData = any> extends GraphEntity {
104
122
  type: 'node';
@@ -127,6 +145,11 @@ interface GraphEdge<TEdgeData = any> extends GraphEntity {
127
145
  sourcePort?: string;
128
146
  /** Port name on the target node this edge connects to. */
129
147
  targetPort?: string;
148
+ /**
149
+ * Per-edge directedness override. When absent, the edge inherits the graph's
150
+ * {@link Graph.mode}.
151
+ */
152
+ mode?: GraphMode;
130
153
  data: TEdgeData;
131
154
  color?: string;
132
155
  }
@@ -154,15 +177,64 @@ interface VisualGraphConfig<TNodeData = any, TEdgeData = any, TGraphData = any,
154
177
  interface DeleteNodeOptions {
155
178
  reparent?: boolean;
156
179
  }
180
+ /**
181
+ * Update payload for {@link updateNode}/`updateEntities`.
182
+ *
183
+ * Optional fields (`x`, `y`, `width`, `height`, `shape`, `color`, `style`,
184
+ * `ports`) accept `null` to **unset** the field. `undefined` (or omitting the
185
+ * key) leaves the field unchanged. `null` is used for unsetting so update
186
+ * payloads stay JSON-serializable.
187
+ */
188
+ interface NodeUpdate<TNodeData = any, TPortData = any> {
189
+ parentId?: string | null;
190
+ initialNodeId?: string | null;
191
+ label?: string | null;
192
+ data?: TNodeData;
193
+ /** New ports for the node, or `null` to remove all ports. */
194
+ ports?: PortConfig<TPortData>[] | null;
195
+ x?: number | null;
196
+ y?: number | null;
197
+ width?: number | null;
198
+ height?: number | null;
199
+ shape?: string | null;
200
+ color?: string | null;
201
+ style?: Record<string, string | number | boolean> | null;
202
+ }
203
+ /**
204
+ * Update payload for {@link updateEdge}/`updateEntities`.
205
+ *
206
+ * Optional fields (`weight`, `mode`, `sourcePort`, `targetPort`, `x`, `y`,
207
+ * `width`, `height`, `color`, `style`) accept `null` to **unset** the field.
208
+ * `undefined` (or omitting the key) leaves the field unchanged. `null` is
209
+ * used for unsetting so update payloads stay JSON-serializable.
210
+ */
211
+ interface EdgeUpdate<TEdgeData = any> {
212
+ sourceId?: string;
213
+ targetId?: string;
214
+ label?: string | null;
215
+ data?: TEdgeData;
216
+ weight?: number | null;
217
+ mode?: GraphMode | null;
218
+ /** Port name on the source node, or `null` to clear the port reference. */
219
+ sourcePort?: string | null;
220
+ /** Port name on the target node, or `null` to clear the port reference. */
221
+ targetPort?: string | null;
222
+ x?: number | null;
223
+ y?: number | null;
224
+ width?: number | null;
225
+ height?: number | null;
226
+ color?: string | null;
227
+ style?: Record<string, string | number | boolean> | null;
228
+ }
157
229
  interface EntitiesConfig<TNodeData = any, TEdgeData = any, TPortData = any> {
158
230
  nodes?: NodeConfig<TNodeData, TPortData>[];
159
231
  edges?: EdgeConfig<TEdgeData>[];
160
232
  }
161
233
  interface EntitiesUpdate<TNodeData = any, TEdgeData = any, TPortData = any> {
162
- nodes?: (Partial<Omit<NodeConfig<TNodeData, TPortData>, 'id'>> & {
234
+ nodes?: (NodeUpdate<TNodeData, TPortData> & {
163
235
  id: string;
164
236
  })[];
165
- edges?: (Partial<Omit<EdgeConfig<TEdgeData>, 'id'>> & {
237
+ edges?: (EdgeUpdate<TEdgeData> & {
166
238
  id: string;
167
239
  })[];
168
240
  }
@@ -258,7 +330,7 @@ type GraphPatch<TNodeData = any, TEdgeData = any> = {
258
330
  } | {
259
331
  op: 'updateNode';
260
332
  id: string;
261
- data: Partial<Omit<NodeConfig<TNodeData>, 'id'>>;
333
+ data: NodeUpdate<TNodeData>;
262
334
  description?: string;
263
335
  } | {
264
336
  op: 'deleteNode';
@@ -271,7 +343,7 @@ type GraphPatch<TNodeData = any, TEdgeData = any> = {
271
343
  } | {
272
344
  op: 'updateEdge';
273
345
  id: string;
274
- data: Partial<Omit<EdgeConfig<TEdgeData>, 'id'>>;
346
+ data: EdgeUpdate<TEdgeData>;
275
347
  description?: string;
276
348
  } | {
277
349
  op: 'deleteEdge';
@@ -352,4 +424,4 @@ interface TransitionOptions<TState, TEvent> {
352
424
  id?: string;
353
425
  }
354
426
  //#endregion
355
- export { VisualEdge as A, NodeConfig as C, SinglePathOptions as D, PortDirection as E, VisualNode as F, VisualPort as I, WalkContext as L, VisualGraphConfig as M, VisualGraphEntity as N, TransitionOptions as O, VisualGraphFormatConverter as P, WalkOptions as R, NodeChange as S, PortConfig as T, GraphPatch as _, EdgeChange as a, GraphStep as b, EntitiesUpdate as c, GraphConfig as d, GraphDiff as f, GraphNode as g, GraphFormatConverter as h, DeleteNodeOptions as i, VisualGraph as j, TraversalOptions as k, EntityRect as l, GraphEntity as m, AllPairsShortestPathsOptions as n, EdgeConfig as o, GraphEdge as p, CoverageStats as r, EntitiesConfig as s, AStarOptions as t, Graph as u, GraphPath as v, PathOptions as w, MSTOptions as x, GraphPort as y, WeightedWalkOptions as z };
427
+ export { SinglePathOptions as A, WalkContext as B, MSTOptions as C, PathOptions as D, NodeUpdate as E, VisualGraphConfig as F, WeightedWalkOptions as H, VisualGraphEntity as I, VisualGraphFormatConverter as L, TraversalOptions as M, VisualEdge as N, PortConfig as O, VisualGraph as P, VisualNode as R, GraphStep as S, NodeConfig as T, WalkOptions as V, GraphMode as _, EdgeChange as a, GraphPath as b, EntitiesConfig as c, Graph as d, GraphConfig as f, GraphFormatConverter as g, GraphEntity as h, DeleteNodeOptions as i, TransitionOptions as j, PortDirection as k, EntitiesUpdate as l, GraphEdge as m, AllPairsShortestPathsOptions as n, EdgeConfig as o, GraphDiff as p, CoverageStats as r, EdgeUpdate as s, AStarOptions as t, EntityRect as u, GraphNode as v, NodeChange as w, GraphPort as x, GraphPatch as y, VisualPort as z };
@@ -0,0 +1,190 @@
1
+ //#region src/validate.ts
2
+ function getDuplicateIndexes(items, getKey) {
3
+ const indexesByKey = /* @__PURE__ */ new Map();
4
+ items.forEach((item, index) => {
5
+ const key = getKey(item);
6
+ if (key == null) return;
7
+ const indexes = indexesByKey.get(key) ?? [];
8
+ indexes.push(index);
9
+ indexesByKey.set(key, indexes);
10
+ });
11
+ for (const [key, indexes] of indexesByKey) if (indexes.length < 2) indexesByKey.delete(key);
12
+ return indexesByKey;
13
+ }
14
+ /**
15
+ * Returns the parent cycles in a graph's hierarchy, each cycle reported once
16
+ * as the list of node ids forming the cycle (cycle members only — nodes whose
17
+ * ancestry merely *leads into* a cycle are not included).
18
+ */
19
+ function getParentCycles(nodesById) {
20
+ const cycles = [];
21
+ const visited = /* @__PURE__ */ new Set();
22
+ for (const startId of nodesById.keys()) {
23
+ if (visited.has(startId)) continue;
24
+ const path = [];
25
+ const positions = /* @__PURE__ */ new Map();
26
+ let currentId = startId;
27
+ while (currentId != null && !visited.has(currentId) && nodesById.has(currentId)) {
28
+ const position = positions.get(currentId);
29
+ if (position !== void 0) {
30
+ cycles.push(path.slice(position));
31
+ break;
32
+ }
33
+ positions.set(currentId, path.length);
34
+ path.push(currentId);
35
+ currentId = nodesById.get(currentId).parentId;
36
+ }
37
+ for (const id of path) visited.add(id);
38
+ }
39
+ return cycles;
40
+ }
41
+ /**
42
+ * Validates the structural invariants of a graph and returns the issues
43
+ * found, or `[]` when the graph is valid. Pure — never throws, never mutates.
44
+ *
45
+ * This is the recommended gate for untrusted or imported graphs (e.g. parsed
46
+ * from a file or received over the wire) before handing them to queries and
47
+ * algorithms: the mutation APIs (`addNode`, `addEdge`, `updateNode`, …)
48
+ * validate incrementally, but `createGraph` does **not** — it accepts
49
+ * dangling `parentId`/edge references and even `parentId` cycles as-is.
50
+ *
51
+ * For Zod-based shape validation of arbitrary unknown values, see
52
+ * `validateGraph` in `@statelyai/graph/schemas` (which reuses these checks).
53
+ *
54
+ * Issue codes: `duplicate-node-id`, `duplicate-edge-id`,
55
+ * `missing-initial-node`, `missing-parent`, `missing-node-initial`,
56
+ * `duplicate-port-name`, `parent-cycle`, `dangling-edge-endpoint`,
57
+ * `missing-source-port`, `missing-target-port`.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const graph = createGraph({
62
+ * nodes: [{ id: 'a', parentId: 'ghost' }],
63
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
64
+ * });
65
+ * getGraphIssues(graph);
66
+ * // => [
67
+ * // { code: 'missing-parent', message: '...', path: ['nodes', 0, 'parentId'] },
68
+ * // { code: 'dangling-edge-endpoint', message: '...', path: ['edges', 0, 'targetId'] },
69
+ * // ]
70
+ * ```
71
+ */
72
+ function getGraphIssues(graph) {
73
+ const issues = [];
74
+ const nodeIndexes = /* @__PURE__ */ new Map();
75
+ const nodesById = /* @__PURE__ */ new Map();
76
+ graph.nodes.forEach((node, index) => {
77
+ nodeIndexes.set(node.id, index);
78
+ nodesById.set(node.id, node);
79
+ });
80
+ for (const [id, indexes] of getDuplicateIndexes(graph.nodes, (node) => node.id)) for (const index of indexes) issues.push({
81
+ code: "duplicate-node-id",
82
+ message: `Duplicate node id "${id}". Node ids must be unique; rename or remove the duplicates.`,
83
+ path: [
84
+ "nodes",
85
+ index,
86
+ "id"
87
+ ]
88
+ });
89
+ for (const [id, indexes] of getDuplicateIndexes(graph.edges, (edge) => edge.id)) for (const index of indexes) issues.push({
90
+ code: "duplicate-edge-id",
91
+ message: `Duplicate edge id "${id}". Edge ids must be unique; rename or remove the duplicates.`,
92
+ path: [
93
+ "edges",
94
+ index,
95
+ "id"
96
+ ]
97
+ });
98
+ if (graph.initialNodeId && !nodesById.has(graph.initialNodeId)) issues.push({
99
+ code: "missing-initial-node",
100
+ message: `Graph initialNodeId references missing node "${graph.initialNodeId}". Add that node or update the graph's initialNodeId.`,
101
+ path: ["initialNodeId"]
102
+ });
103
+ graph.nodes.forEach((node, index) => {
104
+ if (node.parentId != null && !nodesById.has(node.parentId)) issues.push({
105
+ code: "missing-parent",
106
+ message: `Node "${node.id}" has parentId "${node.parentId}", which does not exist. Add that node or remove the parentId.`,
107
+ path: [
108
+ "nodes",
109
+ index,
110
+ "parentId"
111
+ ]
112
+ });
113
+ if (node.initialNodeId && !nodesById.has(node.initialNodeId)) issues.push({
114
+ code: "missing-node-initial",
115
+ message: `Node "${node.id}" has initialNodeId "${node.initialNodeId}", which does not exist. Add that node or remove the initialNodeId.`,
116
+ path: [
117
+ "nodes",
118
+ index,
119
+ "initialNodeId"
120
+ ]
121
+ });
122
+ for (const [name, portIndexes] of getDuplicateIndexes(node.ports ?? [], (port) => port.name)) for (const portIndex of portIndexes) issues.push({
123
+ code: "duplicate-port-name",
124
+ message: `Duplicate port name "${name}" on node "${node.id}". Port names must be unique per node; rename or remove the duplicates.`,
125
+ path: [
126
+ "nodes",
127
+ index,
128
+ "ports",
129
+ portIndex,
130
+ "name"
131
+ ]
132
+ });
133
+ });
134
+ for (const cycle of getParentCycles(nodesById)) {
135
+ const chain = [...cycle, cycle[0]].join(" → ");
136
+ issues.push({
137
+ code: "parent-cycle",
138
+ message: `Parent cycle detected: ${chain}. Break the cycle by changing the parentId of one of these nodes.`,
139
+ path: [
140
+ "nodes",
141
+ nodeIndexes.get(cycle[0]) ?? 0,
142
+ "parentId"
143
+ ]
144
+ });
145
+ }
146
+ graph.edges.forEach((edge, index) => {
147
+ const source = nodesById.get(edge.sourceId);
148
+ const target = nodesById.get(edge.targetId);
149
+ if (!source) issues.push({
150
+ code: "dangling-edge-endpoint",
151
+ message: `Edge "${edge.id}" has sourceId "${edge.sourceId}", which references a missing node. Add that node or fix the edge's sourceId.`,
152
+ path: [
153
+ "edges",
154
+ index,
155
+ "sourceId"
156
+ ]
157
+ });
158
+ if (!target) issues.push({
159
+ code: "dangling-edge-endpoint",
160
+ message: `Edge "${edge.id}" has targetId "${edge.targetId}", which references a missing node. Add that node or fix the edge's targetId.`,
161
+ path: [
162
+ "edges",
163
+ index,
164
+ "targetId"
165
+ ]
166
+ });
167
+ if (source && edge.sourcePort !== void 0 && !source.ports?.some((port) => port.name === edge.sourcePort)) issues.push({
168
+ code: "missing-source-port",
169
+ message: `Edge "${edge.id}" has sourcePort "${edge.sourcePort}", but source node "${edge.sourceId}" has no port with that name. Add the port or fix the edge's sourcePort.`,
170
+ path: [
171
+ "edges",
172
+ index,
173
+ "sourcePort"
174
+ ]
175
+ });
176
+ if (target && edge.targetPort !== void 0 && !target.ports?.some((port) => port.name === edge.targetPort)) issues.push({
177
+ code: "missing-target-port",
178
+ message: `Edge "${edge.id}" has targetPort "${edge.targetPort}", but target node "${edge.targetId}" has no port with that name. Add the port or fix the edge's targetPort.`,
179
+ path: [
180
+ "edges",
181
+ index,
182
+ "targetPort"
183
+ ]
184
+ });
185
+ });
186
+ return issues;
187
+ }
188
+
189
+ //#endregion
190
+ export { getGraphIssues as t };